What better ways to pass arguments to filters in Rails controllers?
EDIT: The filter has a different behavior depending on the parameters passed to it, or depends on the parameters to perform its action.
I have an example in my app, where a filter determines how the data is sorted. This filter has a klass param and calls klass.set_filter(param[:order]) to determine :order in the search.
You have to use procs for this.
class FooController < ApplicationController
before_filter { |controller| controller.send(:generic_filter, "XYZ") },
:only => :edit
before_filter { |controller| controller.send(:generic_filter, "ABC") },
:only => :new
private
def generic_filter type
end
end
Edit
One more way to pass the parameter is to override the call method of ActionController::Filters::BeforeFilter.
class ActionController::Filters::BeforeFilter
def call(controller, &block)
super controller, *(options[:para] || []), block
if controller.__send__(:performed?)
controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
Now you can change your before_filter specification as follows
class FooController < ApplicationController
# calls the generic_filter with param1= "foo"
before_filter :generic_filter, :para => "foo", :only => :new
# calls the generic_filter with param1= "foo" and param2="tan"
before_filter :generic_filter, :para => ["foo", "tan"], , :only => :edit
private
def generic_filter para1, para2="bar"
end
end
I -think- you are looking for the use of successive named_scope filters, but I am not sure. We need more information, if that's not what you need.
Related
I have two implementation of one simple controller.
In first implementation every thing works fine, action show executing only when cache missing.
But I understand that set object #page in Proc of cache action it is a bad idea.
That's why I have second implementation, which looks much better.
It works too and returns cached view.
BUT, I can't understand why when I use before_filter, the action show is still executing even when cache hit. In log I see current time.
Could you explain me why?
please.
Implementation 1
class Frontend::StaticPagesController < Frontend::FrontendController
caches_action :show, :cache_path => Proc.new {
#page = StaticPage.find_in_cache(params[:permalink])
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
end
Implementation 2
class Frontend::StaticPagesController < Frontend::FrontendController
before_filter :set_page, :show
caches_action :show, :cache_path => Proc.new {
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
def set_page
#page = StaticPage.find_in_cache(params[:permalink])
end
end
P.S. Rails '4.2.3'
This line
before_filter :set_page, :show
Defines :show as a filter. This is why it runs.
My guess is that you want to define the :set_page filter to run only for show action. In this is indeed your intention, use:
before_filter :set_page, only: :show
PS: _filter filters are deprecated. Use _action filters instead, like before_action.
I need to set up a before_filter in my ApplicationController that redirects a user if they have not yet agreed to a new terms of service. However, I want to restrict the filter to only run based on the type of request.
I'd like to write something like this:
class ApplicationController
before_filter :filter, :only => {:method => :get}
Is something like this possible?
before_filter :do_if_get
private
def do_if_get
return unless request.get?
# your GET only stuff here.
end
Or more simply
before_filter :get_filter, if: Proc.new {|c| request.get? }
private
def get_filter
# your GET only stuff here.
end
Compliments to deefours answer, this also work with Sinatra::Request
before do
something if request.get?
end
I am trying to create database logs for all CRUD actions. I know I can go to each controller action and insert the method to get it working. Is there a way to do this more elegantly so the methods get called for all the controllers before a CRUD action takes place. These are the methods I am using:
To create transaction
TransactionLog.create(:contact_id => contact_id) #Create the transaction
To create the changelog
def self.start_logging(current_user, data, action, new_content ={}, old_content ={}, transaction_log_id)
#log = ChangeLog.new(:table => data, :action => action, :new_content => new_content.to_json, :old_content => old_content.to_json, :transaction_log_id => transaction_log_id)
#log.save
end
Try using Filters (Refer Section 8 here : http://guides.rubyonrails.org/action_controller_overview.html)
The before_filter idea is the way to go. What's missing in the other answers is how to apply this to all your controllers. The best option is to create a mixin with the code that defines and activates the filter.
module HasCrudFilter
def self.included(base)
base.class_eval do
before_filter :my_filter, only: [:create, :update, :destroy]
def my_filter
# Your code here...
end
end
end
end
Then in your controllers
class MyModelController < ApplicationController
include HasCrudFilter
end
you can do
before_filter :your_method, only: ['create', 'update' 'destroy']
def your_method
#do the things
end
The respond_with accepts some parameters, e.g. respond_with(#resource, methods: [:method])
These options should be used in every action. So instead of putting it into every method by hand, is there a possibility to set some default options for just this controller?
The easy and customizable way to do this is by creating a new response method that wraps responds_with.
For example:
class ResourcesController < ApplicationController
def index
#resources = Resource.all
custom_respond_with #resources
end
private
def custom_respond_with(data, options={})
options.reverse_merge!({
# Put your default options here
:methods => [ :method ],
:callback => params[:callback]
})
respond_with data, options
end
end
You could, of course, also overwrite the respond_with completely, however, I find it to be much clearer in the code if you change the name of the method. It also will allow you to use a custom_respond_with in most actions but the standard respond_with in one or two if necessary.
Taking this one step further, if you move the custom_respond_with method to the ApplicationController, you can use it in all of your controllers as necessary.
If you want to specify different default options on a per controller basis, you can do so easily:
class ResourcesController < ApplicationController
def index
custom_respond_with Resource.all
end
private
def custom_respond_options
{ :methods => [ :method ] }
end
end
class ApplicationController < ActionController::Base
protected
def default_custom_respond_options
{}
end
def custom_respond_with(data, options={})
options.reverse_merge! default_custom_respond_options
respond_with data, options
end
end
Basically I want to implement a simple Rails extension to define the seriousness of methods in my controller so that I can restrict usage of them appropriately. For example I'd define the default restful actions as so in an abstract superclass:
view_methods :index, :show
edit_methods :new, :create, :edit, :update
destroy_methods :destroy
I'd then down in a non-abstract controller call:
edit_methods :sort
to add in the sort method on that particular controller as being an edit level method.
I could then use a before_filter to check the level of the action currently being performed, and abort it if my logic determines that the current user can't do it.
Trouble is, I'm having trouble working out how to set up this kind of structure. I've tried something like this so far:
class ApplicationController
##view_methods = Array.new
##edit_methods = Array.new
##destroy_methods = Array.new
def self.view_methods(*view_methods)
class_variable_set(:##view_methods, class_variable_get(:##view_methods) << view_methods.to_a)
end
def self.edit_methods(*edit_methods)
class_variable_set(:##edit_methods, self.class_variable_get(:##edit_methods) << edit_methods.to_a)
end
def self.destroy_methods(*destroy_methods)
##destroy_methods << destroy_methods.to_a
end
def self.testing
return ##edit_methods
end
view_methods :index, :show
edit_methods :new, :create, :edit, :update
destroy_methods :destroy
end
The three methods above are different on purpose, just to show you what I've tried. The third one works, but returns the same results no matter what controller I test. Probably because the class variables are stored in the application controller so are changed globally.
Any help would be greatly appreciated.
The problem is that your class variables are inherited, but point to the same instance of Array. If you update one, it will also be updated on all classes that inherited the Array.
ActiveSupport offers a solution to this problem by extending the Class class with several methods to define inheritable class attributes. They are used everywhere internally in Rails. An example:
class ApplicationController
class_inheritable_array :view_method_list
self.view_method_list = []
def self.view_methods(*view_methods)
self.view_method_list = view_methods # view_methods are added
end
view_methods :index, :show
end
Now you can set default values in ApplicationController and override them later.
class MyController < ApplicationController
view_method :my_method
end
ApplicationController.view_method_list #=> [:index, :show]
MyController.view_method_list #=> [:index, :show, :my_method]
You can even use the view_method_list as an instance method on the controllers (e.g. MyController.new.view_method_list).
In your example you didn't define a way to remove methods from the lists, but the idea is to do something like the following (in case you need it):
# given the code above...
class MyController
self.view_method_list.delete :show
end
MyController.view_method_list #=> [:index, :my_method]
I turned it into a plugin like so:
module ThreatLevel
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def roger_that!
class_inheritable_array :view_method_list, :edit_method_list, :destroy_method_list
self.view_method_list = Array.new
self.edit_method_list = Array.new
self.destroy_method_list = Array.new
def self.view_methods(*view_methods)
self.view_method_list = view_methods
end
def self.edit_methods(*edit_methods)
self.edit_method_list = edit_methods
end
def self.destroy_methods(*destroy_methods)
self.destroy_method_list = destroy_methods
end
view_methods :index, :show
edit_methods :new, :create, :edit, :update
destroy_methods :destroy
end
end
end
ActionController::Base.send :include, ThreatLevel
Calling roger_that! on the super_controller where you want it to take effect does the trick.