Set a before action to all member routes? - ruby-on-rails

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

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

If you ask for a before_action without :only or :except options, it will apply to all member actions:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
In this particular case, it will require login from all actions on all controllers, since controllers will inherit from ApplicationController.
You can skip a before_action if you need it (for example, you need to skip require_login if you want to login into the system or sign up) like this:
class LoginsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
end
Source: Rails Guides
So, in your particular case:
You could have one usual UserController:
class UserController < ApplicationController
def index
end
...
/* you define here `index`, `create`, `update`, ... */
def destroy
...
end
end
And you could have a separate controller with all your member actions:
class UserCustomController < ApplicationController
before_action :set_model
def profile
...
end
def preview
end
def custom_member_action
end
...
/* all your member actions */
end
This would be actually better than having a single controller with lots of methods.

Related

Method not required for GET? If so, is it common not to write it?

I'm new to Rails, and I was thinking that a method should be defined for every single route.
However, /hello_world works as long as I write as below:
Rails.application.routes.draw do
get "/hello_world", to: "hello#world"
end
class HelloController < ApplicationController
# no world
end
# app/views/hello/world.html.erb
hello world!
Is it an expected behavior? If so, is it common not to write it?
You only need a controller action if you have any processing / data retrieval that you need to do before you display the view.
So, yes, the method itself isn't necessarily needed.
You might need to retrieve a record in a show action like this...
class CustomersController < ApplicationController
def show
#customer = Customer.find(params[:id])
end
end
but inn some cases, you might have a before_action that does whatever's neeeded for several methods, so you (again) don't need to specify the action method.
class CustomersController < ApplicationController
before_action :set_customer only: [:show, :edit, :update]
private
def set_customer
#customer = Customer.find(params[:id])
end
end
So this is a case where you might have needed to define a method for the action, but you've now made it unneccessary.

Access the except part of before_action in the controller itself

So I have a controller, say
class RandomActionController < ApplicationController
and I have a method check_authorization in ApplicationController that I use in RandomActionController as
before_action :check_authorization, except: [:create, :get_actions, ..]
Now in another action inside RandomActionController, I might be building an array of actions excluding ones in the except section of check_authorization, or whatever. My problem is, how do I get those actions as a hash/array or any other form?
What you are passing to the except part is a literal array, string or symbol. Rails does not let you (afaik) introspect a controller callback to extract the arguments it was declared with.
If you want to be able to a re-use list of actions it you need to link it to an identifier.
For example this a pattern I often use:
class ApplicationController
private
def self.member_actions
[:show, :edit, :destroy, :update]
end
def self.collection_actions
[:new, :index, :create]
end
end
class FooController < ApplicationController
before_action :set_foo, only: member_actions
def self.member_actions
super + [:fuzzle, :wuzzle]
end
end
But you can just as well use constants or anything else that is available in the class context.

Devise: multiple user types

In order do add security in devise, i need to set the "before_filter" thingy, like:
before_filter :authenticate_student!, only: [:new, :edit]
which is great... But my app need two user types... students and teachers. How do i make the controller just check if any of then is authenticate?
like:
before_filter :authenticate_any!, only: [:new, :edit]
How can i archive that?
I am using Ruby 2.2.0, and rails 4.
Just define those methods in your application controller
class ApplicationController < ActionController::Base
def authenticate_student!
authenticate_user! && current_user.student?
end
def authenticate_any!
authenticate_user!
end
end
You may complete the code of how to check student?

Redirecting in Rails routes

I have simply route in my rails application that looks like this:
resources :users, only: :show
And now for example I want to redirect to http://no_present_path.com when user with sended id is not present and when user with seneded id is present redirect to http://present_path.com. Is it any way to do this with routes constraints?
Routes are meant as a simple match between a string representing a part of a url, method and an action within a controller. The best way to achieve what you're after is using a before_action in your controller. Example
class UsersController < ApplicationController
before_action :authenticate_user, only: [:show]
def show
...
end
private
def authenticate_user
redirect_to some_other_path unless id_correct?
end
end

Keep an action in view post out of devise authentication

I have installed devise gem for authentication. I have created a scaffold named Members. I have put
before_filter :authenticate_user!
at the top of the Members controller. but I want to make
Member.Show
action to be out of the authentication. I mean with out signing in any one can see the Members profile.
Thanks
You can add this line in your controller (typically, at the beginning):
class MembersController < YourBaseController
# ...
skip_before_filter :authenticate_user!, only: [:show]
# ...
end
The most elegant way is to use an except filter for this:
class MembersController
...
before_filter :authenticate_user!, except: :show
..
end
This way all of your logic around the filter is contained in one place. You can also pass in an array of actions to exclude:
class MembersController
...
before_filter :authenticate_user!, except: [:show, :another_action]
..
end
For more see: http://apidock.com/rails/ActionController/Filters/ClassMethods/before_filter
You can simply do
class MembersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show]
#rest of the codes
def show
#show codes
end
end

Resources