Set a before action to all member routes?

fotanus picture fotanus · Feb 10, 2014 · Viewed 9.2k times · Source

Playing around with Rails 4, I noticed that it defines a before_action filter set_[model], that is called for the actions show, edit, update and destroy.

It is in fact very useful, but I don't see much sense in not having it to all member actions. After all, if you are using a member action, you want to act on that member, thus you need to recover it from database at some point.

Note that by member actions, I also means the ones configured on routes.rb in the members block.

Is there a straight-forward way to do this, without list all member actions on the before_action filter?

Edit: To clarify, the whole point is use some rails magic to get all member routes and generate the array that would be pass in the :only. So I can do something like

before_action set_model only: all_member_routes

where all_member_routes is a piece of code that returns all member routes for my model.

Or even better,

before_action set_model, only_member_actions: true

Answer

Robin picture Robin · Mar 5, 2014

The show, edit, update, destroy actions are precisely all member actions. They are the only ones that require to find the model.

If you have your own member action, then you'll have to add it to the list yourself.

before_action :set_item, only: [:edit, ..., :my_member_action]

Or, you can use the :except option to exclude all the collection actions that do not need it:

before_action :set_item, except: [:index, :create]

This way, if you add other member actions, you wont have to change anything. Personally, I prefer to be explicit and use :only.

I'm pretty sure there is no easier way to do it, you can't detect all member actions automatically.


edit:

I really don't think you should do that but...

You can access the name of your controller with the controller_name method.

Getting the routes related to the controller:

routes = Rails.application.routes.routes.select { |r| r.defaults[:controller] == controller_name }

Then, I think the best way to see if a route is a member route is that the @parts array includes :id. Maybe you can find a more robust way.

So I would do:

routes.select { |r| r.parts.include?(:id) }.map { |r| r.defaults[:action] }.map &:to_sym

That would give you: [:show, :preview, :my_challenges] for

 resources :users, only: [:index, :show], controller: 'accounts/users' do
   member do
     get :preview
     get :my_challenges
   end
 end

class ApplicationController < ActionController::Base 

    def member_routes
        Rails.application.routes.routes
            .select { |r| r.defaults[:controller] == controller_name && r.parts.include?(:id) }
            .map { |r| r.defaults[:action] }
            .map(&:to_sym)
    end

end

class UsersController < ApplicationController
    before_action set_model, only: member_routes
end