I am having trouble understanding this line that gets automatically generated in the controller when I install Devise:
before_action :set_post, only: [:show, :edit, :update, :destroy]
I tried reading the documentation but I am unable to make sense of what it does. For example what does the :set_post symbol do? What is it part of?
Any explanations or resources where I can go for further reading would be appreciated.
Suppose you have a controller like this:
class PostController < ApplicationController
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
end
You see that in show and edit actions there is the same code, you're breaking the DRY principle, so to avoid code repetitions you set an action (method):
def set_post
#post = Post.find(params[:id])
end
that will be performed before the actions that require that same code:
before_action :set_post, only: [:show, :edit, :update, :destroy]
In the end you'll have a controller like this:
class PostController < ApplicationController
def index
#posts = Post.all
end
def show
end
def edit
end
private
def set_post
#post = Post.find(params[:id])
end
end
:set_post - a method at the end of the controller.
The device does not have anything to do with
Related
I have this in my application_controller
class ApplicationController < ActionController::Base
before_action :login_required, :only => 'users/login'
protect_from_forgery with: :exception
protected
def login_required
return true if User.find_by_id(session[:user_id])
access_denied
return false
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
I want to use the login_required for each controller def method
Is there a better way instead of this?
class UsersController < ApplicationController
before_action :set_user, :login_required, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
login_required
#users = User.all
end
def new
login_required
#user = User.new
end
end
Is there a better way to include login_required for all controllers methods since before_action doesn't seem to work?
I don't know the motivation of your logic, so I'll just focus on how you can solve this particular problem.
You can do something like this:
In your application controller:
class ApplicationController < ActionController::Base
before_action :login_required
private
def login_required
current_params = params["controller"] + "/" + params["action"]
if current_params == "users/new" or current_params == "users/index"
return true if User.find(session[:user_id])
access_denied
return false
end
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
The login_required method will just run only on users controller's index and new action, for the rest, it'll just ignore. Also you can just use User.find() and no need to use User.find_by_id()
Now, in your users_controller.rb, you don't need to mention anything about login_required, everything will happen already in application_controller before coming here.
class UsersController < ApplicationController
before_action :set_user, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
#users = User.all
end
def new
#user = User.new
end
end
Firstly, I'm going to suggest that you use devise for authentication, it's a lot more secure and should deal with this for you.
As for your problem, you should be able to specify the before_action like this:
before_action :set_user, :login_required, only: [:new]
Which you can put in your UserController. However if you want this globally, just put it in the ApplicationController, without the only: key.
If you want to require login for all pages except /users/login, then you almost have it right except you are specifying only: when you should be using except::
class ApplicationController < ActionController::Base
before_action :login_required, except: 'users/login'
...
end
This configuration will be applied to all sub-classes of ApplicationController as well.
I'm using Pundit to authorize actions in my controllers. My first try was to authorize the model in an after_action hoook:
class CompaniesController < InheritedResources::Base
after_action :authorize_company, except: :index
def authorize_company
authorize #company
end
This let me use the default controller actions which define #company so I wouldn't hit the database twice. But, this is bad for destructive actions because it's going to not authorize the action after I've already messed up the database.
So, I've changed to using a before_action hook:
class CompaniesController < InheritedResources::Base
before_action :authorize_company, except: :index
def authorize_company
#company = Company.find(params.require(:id))
authorize #company
end
Now, I'm not allowing unauthorized people to delete resources, etc... but I'm hitting the database twice. Is there anyway to access #company without hitting the database twice?
Since your asking for the "rails way" this is how you would set this up in "plain old rails" without InheritedResources.
class CompaniesController < ApplicationController
before_action :authorize_company, except: [:new, :index]
def new
#company = authorize(Company.new)
end
def index
#companies = policy_scope(Company)
end
# ...
private
def authorize_company
#company = authorize(Company.find(params[:id]))
end
end
If you really want to use callbacks you would do it like so:
class CompaniesController < ApplicationController
before_action :authorize_company, except: [:new, :index]
before_action :authorize_companies, only: [:index]
before_action :build_company, only: [:new]
# ...
private
def authorize_company
#company = authorize(Company.find(params[:id]))
end
def authorize_companies
#companies = policy_scope(Company)
end
def build_companies
#company = authorize(Company.new)
end
end
Yeah you could write a single callback method with three code branches but this has lower cyclic complexity and each method does a single job.
Turns out rails controllers have a resource if the model exists and build_resource for actions like new.
class CompaniesController < InheritedResources::Base
before_action :authorize_company, except: :index
private
def authorize_company
authorize resource
rescue ActiveRecord::RecordNotFound
authorize build_resource
end
end
How to set a before_action method with dynamic params, I keep getting an error wrong number of arguments (0 for 1)
class PagesController < ApplicationController
before_action :set_categories
before_action :redirect_if_path_has_changed, only: [:products, :detail]
def home
end
def products
#category = Category.find(params[:id])
#products = #category.products.order("created_at").page(params[:page]).per(6)
redirect_if_path_has_changed(products_by_category_path(#category))
end
def detail
#product = Product.find(params[:id])
redirect_if_path_has_changed(product_details_path(#product))
end
private
def set_categories
#categories = Category.all
end
def redirect_if_path_has_changed(path_requested)
redirect_to path_requested, status: :moved_permanently if request.path != path_requested
end
end
Thank you before
You can do it like this:
before_action only: [:products, :detail] do
redirect_if_path_has_changed("value")
end
Try this. The above works when you need to set any value or something before the action. In you case you want to first find the product or detail from database then you want to redirect to that path. So before_action just calls before the two actions which is not useful in your case.
class FrogsController < ApplicationController
before_action :find_frog, only: [:edit, :update, :show, :destroy]
after_action :redirect_home, only: [:update, :create, :destroy]
def index
#frogs = Frog.all
end
def new
#ponds = Pond.all
#frog = Frog.new
end
def create
#frog = Frog.create(frog_params)
end
def edit
#ponds = Pond.all
end
def update
#frog.update_attributes(frog_params)
end
def show
end
def destroy
#frog.destroy
end
private
def find_frog
#frog = Frog.find(params[:id])
end
def frog_params
params.require(:frog).permit(:name, :color, :pond_id)
end
def redirect_home
redirect_to frogs_path
end
end
Hi all. I was wondering if someone could explain to me why the update route in rails can't take my after_action of redirecting (custom made method on the bottom) it home. The error that I get when i include update in the after_action is "Missing template frogs/update".
This is going to cause me to manually add a redirect_to frogs_path inside the update method.
thanks!
The after_action callback is triggered after the action has run its course. You cannot use it to render or redirect. Do that within the action itself by calling the method:
def update
...
redirect_home
end
If I generate a scaffold I get the standard actions index, new, show, create .... all of which contain a line e.g. like
#comment = Comment.find(params[:id])
Does it make sense to put this line in a seperate method into the controller like
def load
#comment = Comment.find(params[:id])
end
Is this an advantage? Thx for your time
Yes to the separate method, and also yes to using a before_filter.
class CommentsController < ApplicationController
# Whitelist your before_filter like this. Or you can blacklist it with :except => []
before_filter :load_comment, :only => [:edit, :update, :destroy, :show]
def show
end
def index
#comments = Comment.all
end
def new
#comment = Comment.new
end
# etc ...
protected
def load_comment
#comment = Comment.find(params[:id])
end
end