I’m working what feels like a rather large project using the Neo4j.rb gem (which recently had its 3.0 release!). One feature of this project allows users to share different types of events with other users. Access to an endpoint in the API is based on whether a given user has a relationship to the target and, if so, some properties of that relationship. So, for instance, a User who has a direct relationship to an Event with the right score may see some restricted properties; a User who is related to an object that is related to that Event will see some limited properties; a User with no relationship whatsoever will not even be able to get to the page.

One of the nice things about the way this is setup is that all of the relationships share properties and some behavior, so it’s begging to be abstracted out into a module that I can test once and share with my resources. It also means that if I’m not careful, I’ll have to duplicate a lot of basic setup code: routes, controllers, etc,… I want to do this:

class Api::V1::EventsController & ApplicationController
  has_users_route

  # normal methods
end

…and have it automatically add a `:users` resource under `event`, so I can do `/api/v1/events/:event_id/users/:user_id` and it will route to the `UserSecurity` controller. This will prevent me from having to do this:

namespace :api do
  namespace :v1 do
    resources :events do
      resources :users, to: 'user_security'
    end
    resources :bands do
      resources :users, to: 'user_security'
    end
    # repeat about 15 times
  end
end

The question, then, is… how the hell do I do this? I did some research and found a lot of information on using Engines to add routes to apps, but this didn’t feel quite right. Someone on StackOverflow pointed me this. It was different from what I had in mind in that it was registering the new resources directly from `routes.rb`. At first, I didn’t think it was going to fit. I found a way to make it use a method in my controller, celebrated, began writing tests and this post… and realized that it was wiping out all the existing routes and just adding the new ones! Womp womp.

So I abandoned the idea of calling methods from controllers and I’m sticking it in `routes.rb`. This is smarter because (as we already covered) this really is a routing issue. By calling a method from the routes file, I can very easily manage which resources are taking advantage of this feature.

With all that said, my `UserAuthorization` module ended up looking like this:

module UserAuthorization
  extend ActiveSupport::Concern

  module ClassMethods
    def register_new_resource(controller_name)
      MyApp::Application.routes.draw do
        puts "Adding #{controller_name}"
        namespace :api do
          namespace :v1 do
            resources controller_name.to_sym do
              resources :users, controller: 'user_security', param: :given_id
            end
          end
        end
      end
    end
  end
end

`routes.rb` looks like this:

['events', 'bands', 'and so on'].each { |resource| ApplicationController.register_new_resource(resource) }

Calling `Rails.application.routes.named_routes.helpers` from the Rails console showed that my new resources were added. Victory! My request specs also suddenly changed and showed that my endpoints had come to life. There was a new problem, though, in the form of `params`.

It’s like this: since UserSecurityController is receiving data from any number of endpoints, I have a mystery resource and mystery param ID: `/api/v1/mystery_resource/:mystery_id/users/:id`. My controller actions need an easy way to get access to each of those and find the appropriate models the user is trying to load.

I started by trying to use the `param` option in the routes like this:

resources controller_name.to_sym, param: :target_id do
  resources :users, controller: 'user_security', param: :given_id
end

All that did was given me `param[:mystery_resource_target_id]` and `param[:given_id]`. The mystery resource — the target the user is trying to modify — was still unidentified, it just had `target_id` appended to it. Some more searches indicated that it might not be possible to change this, so I went in the other direction: If I can’t change the param’s key, I can figure out the path taken that ended up at the controller and set the param accordingly. While I was at it, I added a method to help me find the model that is responsible for the target so I can do things like `target_model.where(whatever)`.

Here’s the resultant class.

class Api::V1::UserSecurityController & ApplicationController
  before_action :authenticate_user!
  before_action :target_id

  private
  attr_reader :root_resource

  def target_id
    @target_id ||= get_target_id
  end

  def get_target_id
    @root_resource = request.fullpath.split('/')[3].singularize
    params["#{root_resource}_id".to_sym]
  end

  def target_model
    @target_model ||= root_resource.capitalize.constantize
  end

  def given_id
    params[:given_id]
  end
end

I don’t love how I had to do that but it gets the job done and I can and will always refactor. There’s still a lot to do but this is a start. Hope it gives you some ideas.