I have a rails site with users, and these users can make certain pages.
I am trying to make those pages viewable to people not logged in. So, I am making another view & controller action for the page.
Currently, the only issue I have is making a controller that has one action that requires authentication and another action which does not, while remaining on the same controller.
What I have
class PageController < ApplicationController
before_action :authenticate_user!
skip_before_filter :verify_authenticity_token
respond_to :json
def show
#page = Page.new
....
end
....
end
What I want
class PageController < ApplicationController
if params[:action] != 'show_public'
before_action :authenticate_user!
skip_before_filter :verify_authenticity_token
end
respond_to :json
def show
#page = Page.new
#current_user = current_user
....
end
def show_public
#page = Page.new
....
end
....
end
The error which I currently get is:
undefined local variable or method `params' for PagesController:Class
Note
Someone else made most of the code for this site (and has since left), and I am new to rails. So, if I am going about solving this problem with the entirely wrong approach please let me know.
You can do it like this:
before_action :authenticate_user!, only: [:show]
So this will run only on show action. Sometimes you need to run on all and leave one so:
before_action :authenticate_user!, except: [:show]
So this will not run on show but will run on all other actions. And suppose your before_action is in application controller and you need to skip in the inherited controllers you can do it like this:
skip_before_action :require_login, only: [:show]
Related
I have a controller named HomeController with index and show actions. I want to check if the user subscription has ended and show him a message and redirect to HomeController#index.
Currently i am doing it as below
class HomeController < ApplicationController
before_action :check_if_trial_expired, only: [:index]
before_action :redirect_if_trial_expired, only: [:show]
protected
def check_if_trial_expired
#trial_expired = current_user.trial_expired?
end
def redirect_if_trial_expired
redirect_to home_path if current_user.trial_expired?
end
end
Is there a better way to do this? I want to redirect the user to HomeController#index in case a condition satisfies.
Many Thanks in advance.
You'll at least need the index and show methods defined on the controller; make sure you have them in your routes. I don't think you need to use before_action for the index. Also, you can memoize trial_expired if it is an expensive operation.
class HomeController < ApplicationController
before_action :redirect_if_trial_expired, only: [:show]
def index; end
def show; end
private
def redirect_if_trial_expired
redirect_to home_path if current_user.trial_expired?
end
end
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
I have the following before_actions in my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
before_action :logged_in_user
before_action :admin_user
private
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
And I have skip_before_actions in my PostsController:
class PostsController < ApplicationController
skip_before_action :logged_in_user, except: [:new, :edit, :update, :destroy]
skip_before_action :admin_user, except: [:new, :edit, :update, :destroy]
before_action :find_post, only: [:edit, :update, :show, :delete]
The find_post action works perfectly, and the method is in that controller. I want to be able to access posts#index and posts#show without logging in or being an admin, but everything I try, it won't skip those actions, and I'm redirected to log in. It's working in my other controllers. I worked around the index by routing to static_pages#home and defining that action to render posts/index with skip_before_action in the static_pages controller. In a previous attempt, I tried not putting the before action in ApplicationController, and just calling before_action in PostsController and UsersController when I need it, but PostsController wasn't doing that either. I wrote a test action to just redirect_to in PostsController and tried calling before_action :test_action on everything, and it wouldn't do that either. What am I missing?
According OOP, logged_in_user method is private so that in PostsController, skip_before_action doesn't find this method.
After Josh Brody took a look at my git file, I found the problem... apologies to everyone, but it seems there was an issue with my IDE instead. It wasn't properly saving my posts controller. Reboot the computer, and everything worked. I went back and switched the logic to instead use before_action. Ha, wasted six hours of my day banging my head against the computer thinking it was my code. Good learning experience.
I have two controllers: pages_controller and charges_controller
pages_controller just has:
class PagesController < ApplicationController
def show
render template: "pages/#{params[:page]}"
end
end
charges_controller has basic stripe info to accept charges
my routes.rb has a root set to my home page and:
get "/:page" => "pages#show"
I have about 8 html files in views/pages/
I want one and only one of those pages, "products.html.erb" to have a before_action authenticate user!
If I add a before action to the pages controller, it affects every page. Need some tips, new developer.
You can create a custom private method which verifies the value for the params[:page] param, and depending if this is the one which you want or not to restrict then apply the devise authenticate_user before filter only in the show method, something like:
class PagesController < ApplicationController
before_action :custom_authenticate_user!, only: :show
# remains equal
def show
render template: "pages/#{params[:page]}"
end
private
# only if params[:page] equal 'bla' then use the authenticate_user!
def custom_authenticate_user!
authenticate_user! if params[:page] == 'bla'
end
Also this is an easier way, just to use if and the only option in order to check the params and make it work, without having to create a new method:
class PagesController < ApplicationController
before_action :authenticate_user!, only: :show, if: -> { params[:page] == 'bla' }
You can single out the action that renders "products.html.erb" using before_action. If it is only rendered in your show action, it would look like:
before_action :authenticate_user, only: [:show]
You can add as many routes as you want in there like:
before_action :authenticate_user, only: [:show, :create, :destroy]
Here are the docs for it.
class PagesController < ApplicationController
def show
if "#{params[:page]}" == "product"
your action here
end
render template: "pages/#{params[:page]}"
end
end
before_action inside controller usually for "same code" between new/show/edit/update, since you just need special code for one page I think you just need if command before render it
add before_action with only: option to pass an array of actions you want to apply the filter. There is another option you can use skip: to pass an array of actions if you want that filter to apply to all the actions except the action names you want to skip
class PagesController < ApplicationController
before_action :authenticate_user!, only: [:show]
def show
render template: "pages/#{params[:page]}"
end
end
I have a devise User and inside I have admin as boolean default to false. How can I fixed my routes in my ruby on rails app for it to give access to certain pages ONLY for the admin who has admin as true.
UPDATE:
I changed followed the first answer I got which said to create a is_admin? method in my controller and specify what actions. However, when I do that, I get a:
undefined method `admin' for nil:NilClass
UPDATE 2:
Products Controller:
class ProductsController < ApplicationController
before_action :is_admin?, only: [:edit, :update, :destroy]
before_action :set_product, only: [:show]
Application Controller:
def is_admin?
if signed_in?
redirect_to root_path unless current_user.admin
end
end
You shouldn't do that in the routes file, the best place to do it's on the controller filtering part. Attention to the :authenticate_user! method being before the is_admin?. Otherwise current_user will be nil.
class PagesController < ApplicationController
before_action :authenticate_user!
before_action :is_admin?, only: [:action1, :action2]
...
private
def is_admin?
unless current_user.is_admin?
flash.alert = "Sorry, you don't have permissions to perform this action."
redirect_to root_path
end
end
end
I recommend you use pundit gem and policies for everything related to authorizations.