Search code examples
ruby-on-railsroutesrefinerycms

Rails Routing: Need to add an optional :user_id to route for user emulation


I'm using Refinery CMS with an extension application that has the following in its routing:

namespace :sub_application do
  resources :employees
  resources :customers
  resources :yet_another_plural_resource
end

which produce the following routes:

/sub_application/employees
/sub_application/employees/:emp_id
/sub_application/customers
/sub_application/customers/:cust_id
/sub_application/yet_another_plural_resource
.... (you get the picture)

Now, most users will be using THESE routes but there are super users that need to view what these people are viewing as they would view it. My solution to this is to pass the emulated user id through the routing system as a match variable like this:

/sub_application/:user_id/employees
/sub_application/:user_id/employees/:emp_id
/sub_application/:user_id/customers
/sub_application/:user_id/customers/:cust_id
/sub_application/:user_id/yet_another_plural_resource
.... (you get the picture)

What do I need to add to my route file so that I can have both a :user_id route version and a regular version?


Solution

  • Bit of a hack, but you can try to use 'classic' nested resources, while hiding away the 'users' prefix.

    namespace :sub_application do
      resources :employees
      resources :customers
      resources :yet_another_plural_resource
    
      # this block MUST be after the other routes
      # the empty path options should remove the 'users/' prefix
      resources :users, path: '' do 
        resources :employees
        resources :customers
        resources :yet_another_plural_resource  
      end
    end
    

    if you want to avoid repeating the routes, you can do this :

    namespace :sub_application do
    
      # store the routes in a lambda
      routes = ->{
        resources :employees
        resources :customers
        resources :yet_another_plural_resource
      }
    
      # apply the routes to current scope
      routes.call
    
      # then apply to nested resources scope
      resources :users, path: '', &routes
    end
    

    The routes will all lead to the same controllers, so you'll have to adapt your logic whether the request is made with or without an user_id.

    If you do not want to route to a UsersController at all, just pass only: [] as an additional option to resources :users

    EDIT

    Actually, there is a simpler way than storing the routes in a lambda to make them available outside of nested scope :

    namespace :sub_application do
      resources :users, path: '', shallow: true do
        resources :employees
        resources :customers
        resources :yet_another_plural_resource
      end
    end
    

    beware that it wont generate all routes by default. See the docs for more info.