I have controller looks like
class BarsController < ApplicationController
after_action :some_method, only: [:index]
def index
get_cache = $redis.get('some_key')
if get_cache.present?
# i want to skip after_action callback in here
else
# other stuff
end
end
end
How can I skip after_action :some_method if get_cache is present? I know I can do this with conditional like this
class BarsController < ApplicationController
after_action :some_method, only: [:index], unless: :check_redis
def index
get_cache = $redis.get('some_key')
if get_cache.present?
# i want to skip after_action callback in here
else
# other stuff
end
end
private
def check_redis
$redis.get('some_key')
end
end
But I think that is redundant, because should multiple get to redis.
This should work:
class BarsController < ApplicationController
after_action :some_method, only: [:index], unless: :skip_action?
def index
get_cache = $redis.get('some_key')
if get_cache.present?
#skip_action = true
# i want to skip after_action callback in here
else
# other stuff
end
end
private
def skip_action?
#skip_action
end
end
You can also use attr_accessor :skip_action instead of private method because controller is just object.
Related
In a Rails app, I have a before_action filter that sends a webhook message. Since I have a few controllers that I want the before_action to act on, is there a good way to make it a module and prepend it?
My current logic is:
# first_controller.rb:
before_action :do_something
def do_something
#same logic
end
# second_controller.rb:
before_action :do_something
def do_something
#same logic
end
# third_controller.rb:
before_action :do_something
def do_something
#same logic
end
If your controllers inherit from ApplicationController, You can do the following:
class ApplicationController < ActionController::Base
def do_something
#same logic
end
end
class FirstController < ApplicationController
before_action :do_something
end
class SecondController < ApplicationController
before_action :do_something
end
class ThirdController < ApplicationController
before_action :do_something
end
Or you can make your own parent controller, eg. DoSomethingController
class DoSomethingController < ApplicationController
before_action do_something
def do_something
#same logic
end
end
class FirstController < DoSomethingController
end
class SecondController < DoSomethingController
end
class ThirdController < DoSomethingController
end
Or you can use #code_aks's answer https://stackoverflow.com/a/59846330/8554172 to make a module and include it.
Yes, it is good to use DRY here. If your controllers do have same parent class you can place that method in there. If not it is good practice to move this method to the module and include it with reusing.
You can try below code write the methods in a controller concern. For example:
# app/controllers/concerns/example_concern.rb
module ExampleConcern
extend ActiveSupport::Concern
protected
def before_filter_1
puts "from first before_filter_method"
end
def before_filter_2
puts "from second before_filter_method"
end
end
Now in the controller, include the module in the concern and call the methods using before_action as required. For example:
# app/controllers/examples_controller.rb
class ExamplesController < ApplicationController
include ExampleConcern
before_action :before_filter_1, only: [:action_a, :action_b, :action_c]
before_action :before_filter_2, only: [:action_d, :action_e]
def action_a
end
def action_b
end
def action_c
end
def action_d
end
def action_e
end
end
Hope this will help you. :)
My codebase contains callbacks like
prepend_before_action :authenticate_api_user! and
before_action :authenticate_api_v1_user!
What is the difference between these two?
Generally before_action runs before every action to a method and
prepend_before_action does what it says. It just add your definition at index zero.
Here is a great use case to prove the same:
class ConfuseUsersController < ApplicationController
prepend_before_action :find_user, only: [:update]
prepend_before_action :new_user, only: [:create]
before_action :save_and_render
def update
end
def create
end
private
def find_user
#user = User.find(params[:id])
end
def new_user
#user = User.new
end
def save_and_render
persited = #user.persited?
#user.assign_attributes(user_params)
if #user.save
redirect users_path(#user)
else
render (persited ? :edit : :new)
end
end
end
before_action :save_and_render this makes save_and_render to get called before every action.
prepend_before_action :find_user, only: [:update] This prepends find_user function to get called before save_and_render
Another example:
We have an ApplicationController where...
class ApplicationController < ActionController::Base
before_action :one
before_action :three
end
Now in any controller if we want to execute any other method for e.g. two before three you can use prepend_before_action like
prepend_before_action :three, :two
class AdminsController < ApplicationController
prepend_before_action :three, :two
end
Now before three gets executed two will get execute and then three for this specific method.
In my current application I have two doorkeeper scopes, user and admin. In the doorkeeper documentation for setting scopes in an API it shows
class Api::V1::ProductsController < Api::V1::ApiController
before_action -> { doorkeeper_authorize! :public }, only: :index
before_action only: [:create, :update, :destroy] do
doorkeeper_authorize! :admin, :write
end
...
end
I don't want to call doorkeeper in every controller, so in my ApplicationController I have
module API
module V1
class ApplicationController < ActionController::API
before_action { doorkeeper_authorize! :user, :project }
...
end
end
end
but I don't want to give :project access to every controller. Is there a way for me to allow user in our application controller before_action { doorkeeper_authorize! :user } and on a per-controller basis allow project? ie:
module API
module V1
class SomeController < ApplicationController
before_action only: [:index, :show] { doorkeeper_authorize! :project }
...
end
end
end
Use a conditional with controller_name - smith like this:
before_action { doorkeeper_authorize! :project },
if: -> { controller_name == 'some' }
Check if maybe you should pass a param to lambda like:
if: ->(instance) { instance.controller_name == 'some' }
I was able to solve this by doing the following in my API::V1::ApplicationController
module API
module V1
class ApplicationController < ActionController::API
WHITELISTED_PROJECT_CONTROLLERS = %w( projects pre_task_plans
job_hazard_analyses ).freeze
before_action :authorize!
def authorize!
if project_scope?
if !WHITELISTED_PROJECT_CONTROLLERS.include?(controller_name)
return user_not_authorized
end
end
doorkeeper_authorize! :user, :project
end
def project_scope?
doorkeeper_token&.scopes&.any? { |s| s == 'project' }
end
...
end
end
end
maybe creating your own filter might be an option
before_action :doorkeeper_user_authorize!, only: [:create, :update, :destroy]
protected
def doorkeeper_user_authorize!
doorkeeper_authorize!( :user )
end
def doorkeeper_project_authorize!
doorkeeper_authorize!( :user, :project )
end
then in controller where project should be allowed
skip_before_action :doorkeeper_user_authorize!
before_action :doorkeeper_project_authorize!
class ApplicationController < ActionController::Base
before_action :test, only: [:index]
def test
ap 'test'
end
end
The above is run before every single index action, be it dogs#index or cats#index or rabbits#index. How should I get it to execute just before cats#index and rabbits#index?
I want to test to be exectuted before actions in many controllers.
You can skip this method:
class ApplicationController < ActionController::Base
before_action :test, only: [:index]
def test
p 'test'
end
end
class DogsController < ApplicationController
skip_before_action :test
end
Just move your call into the controller you want it to run in.
class ApplicationController < ActionController::Base
# nothing here!
def test
# ...
end
end
class CatsController < ApplicationController
before_action :test, only: [:index]
end
class RabbitsController < ApplicationController
before_action :test, only: [:index]
end
It is pretty simple actually
Create before_action in application_controller and check if: that incoming request is for concerned_controller.
class ApplicationController < ActionController::Base
before_create :assign_setting, only: :create, if: :registration_controller?
def registration_controller?
params["controller"] == "registrations"
end
def assign_settings
# your code
puts "settings applied"
end
end
So I have ApplicationController.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
def decode_email
params[:email] = URI::decode(params[:email])
end
end
and then UsersController.rb:
class UsersController < ApplicationController
before_filter :decode_email, only: [:show]
def show
#blah blah
end
end
Now hitting the show action results in:
undefined local variable or method 'decode_email' for #<UsersController:0x007fb5f216a710>
Why isn't that method being inherited so it can be properly used as a before_filter?
class ApplicationController < ActionController::Base
protect_from_forgery
private
def decode_email
params[:email] = URI::decode(params[:email])
end
end
is working for me