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
Related
I want to create a default scope to filter all queries depending on the current user. Is it possible to pass the current user as an argument to the default_scope? (I know this can be done with regular scopes) If not, what would be another solution?
Instead of using default_scope which has a few pitfalls, you should consider using a named scope with a lambda. For example scope :by_user, -> (user) { where('user_id = ?', user.id) }
You can then use a before_filter in your controllers to easily use this scope in all the actions you need.
This is also the proper way to do it since you won't have access to helper methods in your model. Your models should never have to worry about session data, either.
Edit: how to use the scope in before_filter inside a controller:
before_filter :set_object, only: [:show, :edit, :update, :destroy]
[the rest of your controller code here]
private
def set_object
#object = Object.by_user(current_user)
end
obviously you'd change this depending on your requirements. Here we're assuming you only need a valid #object depending on current_user inside your show, edit, update, and destroy actions
You cannot pass an argument to a default scope, but you can have a default scope's conditions referencing a proxy hash that executes its procs every time a value is retrieved:
module Proxies
# ProxyHash can be used when there is a need to have procs inside hashes, as it executes all procs before returning the hash
# ==== Example
# default_scope(:conditions => Proxies::ProxyHash.new(:user_id => lambda{Thread.current[:current_user].try(:id)}))
class ProxyHash < ::Hash
instance_methods.each{|m| undef_method m unless m =~ /(^__|^nil\?$|^method_missing$|^object_id$|proxy_|^respond_to\?$|^send$)/}
def [](_key)
call_hash_procs(#target, #original_hash)
ProxyHash.new(#original_hash[_key])
end
# Returns the \target of the proxy, same as +target+.
def proxy_target
#target
end
# Does the proxy or its \target respond to +symbol+?
def respond_to?(*args)
super(*args) || #target.respond_to?(*args)
end
# Returns the target of this proxy, same as +proxy_target+.
def target
#target
end
# Sets the target of this proxy to <tt>\target</tt>.
def target=(target)
#target = target
end
def send(method, *args)
if respond_to?(method)
super
else
#target.send(method, *args)
end
end
def initialize(*_find_args)
#original_hash = _find_args.extract_options!
#target = #original_hash.deep_dup
end
private
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if #target.respond_to?(method)
call_hash_procs(#target, #original_hash)
#target.send(method, *args, &block)
else
super
end
end
def call_hash_procs(_hash, _original_hash)
_hash.each do |_key, _value|
if _value.is_a?(Hash)
call_hash_procs(_value, _original_hash[_key]) if _original_hash.has_key?(_key)
else
_hash[_key] = _original_hash[_key].call if _original_hash[_key].is_a?(Proc)
end
end
end
end
end
Then in ApplicationController you can use an around_filter to set/unset Thread.current[:current_user] at the begin/end of each request:
class ApplicationController < ActionController::Base
around_filter :set_unset_current_user
protected
def set_unset_current_user
Thread.current[:current_user] = current_user if logged_in?
yield
ensure
Thread.current[:current_user] = nil
end
end
Suppose I have some logic in a base controller to pass information to the view to build something like a breadcrumb:
class ContextAwareController < ApplicationController
after_filter :build_breadcrumb
def build_breadcumb
#...
end
end
I want this build_breadcrumb method to run after the main controller logic, but before the view is rendered.
The above code runs too late, but a before_filter would be too early.
Can anybody suggest a way to accomplish this without explicitly calling build_breadcumb at the end of each of the actions in the child controllers?
Thanks
I had the same problem and solved it like this:
class ApplicationController < ActionController::Base
def render *args
add_breadcrumbs
super
end
end
There are also some gems to achieve this. One of them is rails3_before_render.
It works similarly to filters, for example:
class PostsController < ApplicationController
before_render :ping, :except => [:destroy]
def index; end
def new; end
def show; end
def destroy; end
private
def ping
Rails.logger.info "Ping-Pong actions"
end
end
(code snipped copied from gem documentation)
I believe rendering starts when render is called, and there's no default way to defer it. Here's one thing you could do:
filters are applied in the same order declared. So make a second after-filter that calls render with an array args stored in a class variable. Then anywhere you would normally call render, set the variable.
If we're overriding render, we're not really using the filter chain at all, so it might be simpler to determine which action we're in using the #_action_name.
StuffController < ApplicationController
def my_filter
# Do the stuff
end
def render(*args)
my_filter if #_action_name == "show"
super
end
end
You can use alias_method_chain like this
alias_method_chain :render, :before_render_action
this will create 2 methods :render_with_before_render_action and :render_without_before_render_action. If you call render, then :render_with_before_render_action will be called. You can override this method
def render_with_before_render_action(*options, &block)
<your code>
render_without_before_render_action(*options, &block)
end
If you don't want your code to be executed and you want to have default render then you should directly call the :render_without_before_render_action
You can do like this to fake a before_render:
class StuffController < ApplicationController
before_filter :my_filter, only: [:index, :show]
def my_filter
#my_filter = true
end
def _my_filter
# Do the actual stuff here
end
def render(*args)
_my_filter if #my_filter
super
end
end
Thanks to #joshua-muheim for the tip about using render
I am fairly new to Ruby on Rails and as a C# developer, when I want to re-use code (for a repository class), I could put it into a base class of type <T> to be able to do something like this:
public virtual IEnumerable<T> GetAll()
{
return Context<T>.GetAll();
}
If I need to do any custom logic, I could, of course, override the method in my 'User' repository.
In Ruby, I am familiar that you can do this:
class UsersController < ApplicationController
This will allow access to all methods in ApplicationController and it's parent classes. When using scaffolding, it generates the following method in each of my child classes:
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
What I end up with is 10 classes that have the same method, but the only difference is 'User.all', 'Post.all', etc.
How would I make this method generic so I can put it in my ApplicationController class?
Thanks for any assistance you can provide to a Ruby on Rails newbie.
The first thing to realize about the scaffolding code is that it can be abreviated, as such:
def index
#users = User.all
end
unless you intend to deliver the view in another format, like json, html, pdf, the respond_to block is unnecessary. If you still feel the need to dry up this method, you could do something like
# app/controllers/concerns/autoload_records.rb
module AutoloadRecords
included do
before_action :load_records, only: :index
before_action :load_record, only: [:create, :show, :edit, :update, :destroy]
end
private
def load_records
#records = model_class.all
end
def load_record
#record = model_class.find(params[:id])
end
def model_class
klass = self.class.to_s[/\A(\w+)sController\Z/,1] #=> get the name of the class from the controller Constant
Object.const_get(klass)
end
end
and write your controller like
class UsersController < ApplicationController
include AutoloadRecords
def index
#records # => #<ActiveRecord::Relation[...]>
end
def show
#record # => #<User ...>
end
def non_rest_action
#record # => nil
#records # => nil
end
end
Rather than doing an eval where you really don't want to be doing one. Check out Jose Valim's Inherited Resources gem. It provides the standard CRUD methods for all of your controllers and is quite sophisticated. It is also thoroughly tested so you don't have to worry about making sure your generic code operates as expected in all cases.
For details on how to use it see the GitHub page linked.
Maybe a simple solution could be to rely on mixins.
You define a module,
module MyModule
def my_index(klass)
#elements = klass.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #elements }
end
end
end
Then, you have in your controller,
include MyModule
def index
my_index(User)
end
Of course, you need to use #elements in your views. If you want a different variable name in each view you can do
def my_index(klass, var_name)
self.instance_variable_set(var_name, klass.all)
...
end
There are several rails plugins that help to reduce this kind of duplication. This one was covered in railscast episode 230.
https://github.com/josevalim/inherited_resources
Based on my experience, you rarely end up with 10 index action looking like #user = User.all. If you know in advance that some actions between different models will be identical - well then may be it makes sense to extract common logic. But then again may be these models are somehow connected? I wouldn't say in advance that Post and User will have identical index actions.
For a short method like this I wouldn't try to eleminate repetition because you may end up losing readability.
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.
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.