I've just upgraded my app to Rails 5.1 and I've been bitten by the new behaviour of skip_before_action callbacks. i.e. if callback is not defined at the time I try to skip it, it raises an error.
I know that I can pass raise: false like
skip_before_action :authorise, raise: false
But wondering if there's a better way to do it.
My main problem is that with eager load set to true, the new behaviour messes up with modular setup of my controllers.
Basically I've got dir app/controllers/api
with module_controller.rb:
module Api
class ModuleController < ActionController::Base
before_action :authorise
end
end
Then I've got app_chats_controller.rb which skips the authorise callback:
module Api
class AppChatsController < ModuleController
skip_before_action :authorise
end
end
With eager load, app_chats_controller.rb gets loaded first, which means callback is not yet defined and without raise: true error is raised.
If I have to bite the bullet and add raise: false to everything, so be it, but surely there's a better way...
Try adding require 'module_controller' to the top of app_chats_controller.rb
Related
Is there a simple way to append to the before_action list for a controller in rails, such that other before_action callbacks added later will run before and not after my callback?
My use is that I have a concern that other Controllers will include, and I want the controllers that include it to be able to add their own before_action methods but then have one of my methods called after all of those, but still before the actual action.
Since it's a pretty big codebase with a bunch of developers, for usability I don't want every user of this concern to have to do prepend_before_action or to have to remember to include my module after they declare their callbacks instead of at the top of the class.
I'm pretty sure there's no builtin way to do this, and I'm not super familiar with metaprogramming in ruby. But is there some way to hook into the internals of the callback list as the callbacks are running and add a new callback to the end or something?
I found a pretty simple solution - looking at the ActiveSupport::Callbacks source, I noticed that the CallbackChain#append_one method which is what is adding the before_action callbacks to the chain is calling a remove_duplicates method every time you add a callback. So I can do the following in my module, overwriting the before_action method:
module MyModule
extend ActiveSupport::Concern
included do
before_action :my_last_callback
end
class_methods do
def before_action(*args, &blk)
method(:before_action).super_method.call(*args, &blk)
method(:before_action).super_method.call(:my_last_callback)
end
end
def my_last_callback
# do stuff after all before_action callbacks
end
end
Now the child class can add as many before_action callbacks as they like, and they'll maintain their order but my_last_callback always gets called last. (There will now be extra insertions/deletions in the callback chain and now it's probably O(n^2) instead of O(n) in the number of callbacks, but I don't anticipate there ever being enough callbacks to make this an issue).
Does around_action in ApplicationController::Base include before_action and after_action? I understand that around_action wraps around the specified action, but would like to know if it also wraps around the before, and after callbacks associated with that action.
For instance, let's look at a code modified from Rails documentation:
class ChangesController < ApplicationController
before_action :some_callback, only: show
around_action :wrap_in_transaction, only: :show
...
private
def wrap_in_transaction
yield unless true
end
end
Will some_callback ever be executed?
Yes, some_callback will be executed. These methods are completely unaware of one another, and will be performed in the order they are written.
It may interest you to see the code that rails uses to do this. You can find it at https://github.com/rails/rails/blob/cdaab2c479c819b04cc72a97c52b804832365cef/actionpack/lib/abstract_controller/callbacks.rb#L180. You will notice that they both call the _insert_callbacks method (https://github.com/rails/rails/blob/cdaab2c479c819b04cc72a97c52b804832365cef/actionpack/lib/abstract_controller/callbacks.rb#L87).
Also, why not just try it using console output or something? This type of thing should be very easy to verify with a quick trial (which is, I suspect, the reason behind this question's downvote).
I'm using Intercom rails in my application and I would like to not include intercom script in a certain situation. So, I would like to skip the intercom after_filter when a value is set in the user session.
I tried that, but it didn't worked:
class ApplicationController < ActionController::Base
before_filter :verify_session
def verify_session
if skip_intercom?
self.class.skip_after_filter :intercom_rails_auto_include
end
end
end
Any idea if it's possible?
#Rafael Caricio i faced the same issue but later i have added the below filter in all of my controllers where i don't want intercom to add:
skip_after_filter :intercom_rails_auto_include
and it is working fine for me
Where is the intercom_rails_auto_include being set? Because Could you not just put skip_after_filter_intercom_rails_auto_include at the top your of your application controller. Or alternatively try and do the following skip_after_filter IntercomRails::AutoIncludeFilter at the top of your controller.
can anyone tell me how to skip all before filters in rails 3.
In rails 2.x we could do
skip_filter filter_chain
however filter_chain is no longer supported in rails 3.
Thanks
Try this
skip_filter *_process_action_callbacks.map(&:filter)
Method _process_action_callbacks should return CallbackChain instance, which is an array of Callbacks
And since Callback#filter gets the name of the callback, this works:
before_filter :setup
_process_action_callbacks.map(&:filter) #=> [:setup]
Need to skip all filters is likely caused by inheriting from a custom ApplicationController.
class ApplicationController < ActionController::Base
# defines multiple `before_filter`s common to most controllers
class SomeController < ApplicationController
# this controller may be fine with inheriting all filters
class AnotherController < ApplicationController
# but this one not!
In my example scenario, instead of removing the before_filters from AnotherController, just let it inherit from ActionController::Base.
Haven't tried it, but this might work:
[:before, :after, :around].each {|type| reset_callbacks(type)}
If you want to specify :only or :except to skip_filter, use the following :
skip_filter(:only => [:method1, :method2], *_process_action_callbacks.map(&:filter))
Xavier Shay put me on the right direction, but then I struggled a bit to figure out I had to put :only before the list of filters to skip!
Edit : the solution above is for RUby 1.8.7. For Ruby 1.9 you'd do :
skip_filter(*_process_action_callbacks.map(&:filter), :only => [:method1, :method2])
For Rails 5, I've used
class SomeController < ApplicationController
skip_before_action *_process_action_callbacks.map{|callback| callback.filter if callback.kind == :before}.compact
You must check the callback.kind, otherwise _process_action_callbacks will return all callbacks, including after and around, which will raise an exception with skip_before_action method.
The .compact at the end removes nils from the resulting array.
This is certainly hacking deep inside Rails belly and you would be better off specifying the callbacks by hand, but the following line will do the job:
eval %[skip_filter #{_process_action_callbacks.select { |c|
[:before, :after, :around].include? c.kind }.collect{|c|
c.filter.inspect}.join(", ")
}]
You can also add :only => :index etc modifications to the eval just before the ending ] to further modify the call if needed.
skip_before_filters
http://ap.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html#M000132
Our application is developed using Rails 2.3.5 along with ActiveScaffold. ActiveScaffold adds quite a bit of magic at run time just by declaring as following in a controller:
class SomeController < ApplicationController
active_scaffold :model
end
Just by adding that one line in the controller, all the restful actions and their corresponding views are made available due to ActiveScaffold's meta programming. As most of the code is added at runtime, in development mode requests seems to be little slower as there is no class_caching.
We needed to add a authorization layer and my team has chosen Lockdown plugin which parses an init.rb file where you declare all the authorization rules. The way that Lockdown stores the authorization rules is by parsing the init.rb file and evaluating the controllers declared in the init.rb file. So for every request Lockdown evaluates all the controllers thereby forcing ActiveScaffold to add lot of meta programming which in turn makes db queries to find out the column definitions of every model. This is considerably slowing down the request in development as there is no class_caching. Some times are requesting are taking almost 30-45 seconds.
Is there any way to force ActiveScaffold to do its magic in a before_filter? Something like the following:
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.instance_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.class_eval do
active_scaffold :model
end
end
end
class SomeController < ApplicationController
before_filter :init_active_scaffold
private
def init_active_scaffold
self.class.instance_eval do
active_scaffold :model
end
end
end
I tried all the above four options, when I make a request, browser seem to show the loading indicator but nothing is happening.
Any help is appreciated.
Thanks in advance.
Lockdown only reparses init.rb in development mode so you can make changes without restarting the application. It will be slower - a convenience trade off. Good news is that Lockdown will only do this parsing once in production mode.
I don't use ActiveScaffold, so I can't offer any help there, but thought this would be of interest to you.