Run Rails controller before_action callback after others - ruby-on-rails

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).

Related

Pass constant to rails before filter exceptions

I've got several before filters similar to:
before_filter :setup_plan, except: :some_action, :another_action, :yet_another_action
But I have a 3 or 4 exceptions to many before filters. At first I was going to change the logic in all of my before_filter methods, but I found myself repeating a lot of code. In a more perfect world I would have a seperate controller for the actions I want to add, but I've got to add the actions in the same controller, and work around the structure that's already in place.
What I want to do is create a constant or variable with the methods I want to skip in my before filters, such as
ACTIONS_TO_SKIP = [:some_action, :another_action, :yet_another_action]
And then simply use before_filter :setup_plan, except: ACTIONS_TO_SKIP
I get an uninitialized constant error when doing this. I tried moving this to a method in a helper and also got an undefined method error.
I know I'm missing something, but can't figure it out. Is there really no way to pass an method or constant that returns an array of methods I want to skip in my before filters? Just trying to make my controller a little bit cleaner and less verbose.
You will need to declare the constant BEFORE the call to skip. The file is interpreted top down.
class FoosController
ACTIONS_TO_SKIP = [:yes, :this, :and, :something, :more]
before_filter :setup_plan, except: ACTIONS_TO_SKIP
end
Note that in recent Rails versions this is now called before_action:
Rails 4: before_filter vs. before_action
(perhaps you want to upgrade...)
You can try something like this:
before_filter :my_before_filter, if: :action_to_skip?
def action_to_skip?
%w[some_action another_action yet_another_action].member?(action_name)
end

prevent controllers having same method redundancy in Rails

Current I have three controllers that uses the exact same authorize_journey method (located in the controllers). Each controller also calls the exact same before_filter :authorize_journey. What is the best way to reduce this kind of redundancy through best-practice?
Also, how can I, if possible, stick to the fat-model-skinny-controller practice?
If the authorize_journey methods are all identical, then you can move a copy to '~/app/controllers/application_controller.rb' and remove it from all the individual controllers.
The before_filters can all remain as they are.
Of course, if the methods are not identical, you may require some refactoring to generalize it further. If you post the code, we can comment further.
You can always use it in the application_controller.rb.
Move the authorize_journey method to application_controller.
Say if you have 4 controllers and you need the before_filter in 3 controller. Then you can define the before_filter :authorize_journey in the the application_controller and use:
skip_before_filter :authorize_journey
in the 4th controller where you don't want the before_filter
I would suggest adding the method to a concern and include them in the controllers that requires the authorize_journey before_filter.
Code for concern will be:
# controllers/concerns/authorize_journery.rb
module AuthorizeJourney
extend ActiveSupport::Concern
included do
before_filter :authorize_journey
end
protected
def authorize_journey
# Code goes here
end
end
Now in the controllers, include the AuthorizeJourney module.
# controllers/examples_controller.rb
class ExamplesController < ApplicationController
include AuthorizeJourney
# Code goes here
end

Rails around_action includes before, and after callbacks?

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).

Ruby on Rails before_filter vs ruby's initialize

Just a thought in my mind. what is the difference the following
before_filter
class ApplicationController < ActionController::Base
before_filter :foo
def foo
#mode = Model.new
end
end
ruby initialize
class ApplicationController < ActionController::Base
def initialize
foo
end
def foo
#mode = Model.new
end
end
Does ruby's initialize method work as expected in rails?
If yes, then can we use initialize in place a filter has to be applied to all actions in a controller?
For each request, you do get a fresh instance of ApplicationController, but the big no-no here is that you're attempting to override core behavior of ActionController::Base#initialize without calling the parent behavior.
ApplicationController < ActionController::Base
def initialize
super # this calls ActionController::Base initialize
init_foo
end
private
def init_foo
#foo = Foo.new
end
end
This is not idiomatic Rails behavior though. They give you before_filter for a reason; so use it.
I believe this was covered in Practical Object-Oriented Design in Ruby by Sandi Metz.
Suppose you are designing a base class for other developers/users, and want to allow them to hook into various steps in a procedure (e.g. initialization.) In general, there are two ways:
Break up the procedure into small methods, and (in the documentation) remind users to use super whenever they override a method.
Include calls to various empty hook methods that users can override with custom functionality.
(I believe that these are variations of the Template Method pattern.)
The second way requires more effort on your part and less effort for your users.
In this specific case, before_filter provides a cleaner way to attach multiple hooks and encourages you to break up hooks into single-responsibility methods with meaningful names. This becomes more important in applications that use more controller inheritance.

Calling a Method from Within a Ruby Class? (or is this rails magic)

I'm new to Ruby and working through some tutorials/screencasts. I've reached the section where they're discusisng the before_filter callback, and it's using some syntax that's a little weird for me. I don't know if it's a feature of ruby, of if it's some rails magic, and was hoping someone here could set me straight or point me in the right direction w/r/t the manual
This is a code fragment from the screencast I'm watching
class MachinesController < ApplicationController
#...
before_filter :login_required, :only => [:report]
#...
def index
#etc...
end
def login_required
#etc...
end
end
In the context of rails, I understand that before_filter is a callback that will fire login_required method when the report action is called. However, it's not clear to me what it is within the context of ruby. In other languages classes typically contain methods, properties, class variables and constants defined within the braces.
However, this looks like its a function call inside the class, and some experiments have show that you can put code in your class definitions and have it called when the program runs. Is this correct? If so, are there special contextual rules for code that's put inline into a class like that? (i.e. would the before_filter function in rails know what class it was called from) If not, what magic is rails doing here?
before_filter is a not actually a callback. It's a class method of ActiveRecord::Base that sets up a callback when you call it. So in this example:
before_filter :login_required, :only => [:report]
When the class is loaded, the method is called, and it adds :login_required to the filter chain for the report method.
The convention for these types of calls is to drop parens, but it would work just fine (and would be more easily identifiable as a method call) if you did this:
before_filter(:login_required, :only => [:report])
Unlike in some other languages, in Ruby the class can be modified at runtime and you can make function calls from within the class definition. So what's happening in this case is that you are calling the before_filter function, which then modifies the MachinesController class definition at runtime.
This is the mechanism which allows for the strikingly beautiful (to my eyes, at least) syntax that you get with Rails where it almost looks like you're using some sort of Domain Specific Language to describe your models. Stuff like the validates, has_many and belongs_to function calls on model classes.
My understanding is that this is called a macro and falls under the umbrella of meta programming. You can read more about this topic.
Ruby is so cool. You can definitely send messages from within a class block. As I understand it, what things like class do, other than the obvious, is control the identity of self. Thus, you should be able to call any method of the class or included modules from there.
I'm not sure exactly what your question is, but here's my interpretation:
before_filter
This is a class method call see ActionController
:login_required
This is a parameter to the before_filter method, and used as a callback using Object#send
:only => [:report]
This is an additional Hash parameter to before_filter see ActionController again.
I'd also suggest looking at the implementation of the before_filter method for insight.

Resources