So I have a method and corresponding partial for including a set of random photos in the sidebar of certain areas of our site.
Right now I have a random_photos method in ApplicationController set with a before_filter.
That works in the sense that it makes the contents of the random_photos method available wherever I need it, but it also unnecessarily executes some complex SQL queries when I don't know it too (ie, when I don't need to access those random photos).
So, how can I limit the accessing of the random_photos method to only when I really need it?
You can add an :if condition to the before_filter call, like so:
class ApplicationController < ActiveController::Base
before_filter :random_photos, :if => is_it_the_right_time?
Yet another option is to use skip_before_filter. It just depends in how many controllers you want to be different. Use skip_before_filter if there are only a handful of controllers you want to be the exception. Use one of the other suggestions if there are many controllers where you want to bypass the filter.
class ApplicationController < ActiveController::Base
before_filter :random_photos
def random_photos
#photos = Photo.random
end
end
class OtherController < ApplicationController
skip_before_filter :random_photos
...
end
You can keep the random_photos method in ApplicationController, and put the before_filters in your other controllers.
class ApplicationController < ActiveController::Base
...
def random_photos
#photos = Photo.random
end
end
class OtherController < ApplicationController
before_filter :random_photos, :only => 'show'
...
end
It depends on how many functions are making use of random_photos...
If a handful then use vrish88's approach but with an after_filter:
class ApplicationController < ActiveController::Base
after_filter :random_photos, :if => is_it_the_right_time?
...
private
def is_it_the_right_time?
return #get_random_photos
end
end
class SomeController < ApplicationController
def show
#get_random_photos = true
...
end
end
If every function in a controller will make use of it then use the skip_before_filter or move the before_filter in the controller and out of the application controller.
Many ways to get it done, and none is more correct then the next. Just try to keep it as simple and transparent as possible so you don't recreate the functionality months down the road because you forgot where all the pieces are located.
Related
Is it possible to skip a before filter in using skip_before_filter when the before_filter is defined with a block. For example:
class ApplicationController < ActionController::Base
before_filter do |controller|
# filter stuff
end
end
I know, that is it possible using the "normal" way of defining the filter with a method name.
I actually found a way around this using an if option with a method. Use
class ApplicationController < ActionController::Base
before_filter(:if => :use_filter?) do |controller|
# filter stuff
end
private
def use_filter?
true
end
end
class OtherController < ApplicationController
private
def use_filter?
false
end
end
:D
Its most definitely not possible. From the documentation:
Note that skipping uses Ruby equality, so it’s impossible to skip a callback defined using an anonymous proc using #skip_filter
http://apidock.com/rails/AbstractController/Callbacks/ClassMethods/skip_action_callback
I hope this is something obvious that I've just consistently overlooked and the community can set me on the right path.
I have a news article controller, but I want to be able to use a "common" ticker list on different views. How do I initialise this "#article_list" if I'm using the partial in a few controllers? Apparently it is of the opinion that using a helper is not the solution, since helpers are just for view logic. So where do I put this initialiser that would be available to every controller as required? I shouldn't put them in application controller should I?
You can use before_filter method, i.e. something like this:
class ApplicationController < ActionController::Base
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
UPDATE:
Indeed, there will be problem with a fat ApplicationController. To avoid it it's possible to use module (almost #carolclarinet describe it below):
module ArticleList
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
And
You can create, essentially, a query object that is only responsible for returning what you need for #article_list, for example, building off of Psylone's answer:
class ArticleList
def ticker_articles
ArticleList.all # or any onther selection
end
end
This class could go in lib, app/models, app/query_objects, app/models/query_objects, wherever it makes sense for you. This is a bit outside The Rails Way so there's no convention about where these types of objects should live.
Then in whatever controller you need this, do:
#article_list = ArticleList.new.ticker_articles
For more explanation of query objects, see this codeclimate article #4. Depending on what you're doing to set #article_list, this might also be called a service object (#2) or something else entirely. No matter what you call it though, its responsibility would be to return the value you need for #article_list and that's it.
I'm making a control panel (user accounts) in rails.
in the layout I need to show things like messages or notifications (facebook-like style).
the problem is these things require an access to database and I'm not sure where to put this code because it's not related to a controller, but the layout is shared with multiple controlers.
so where is the best place to put the code to fetch messages from database should I put in the layout itself (I don't think its right), or as helper ?
the best solution was to build a control-panel controller which handles authentication and permissions and loads common user data from database such as messages...
here is an example code
class ControlPanelController < ApplicationController
before_filter :authenticate_user!
before_filter :get_user_data
helper_method :mailbox
authorize_resource
protected
def get_user_data
#header_conversations=mailbox.inbox.limit(3)
#uevents= Event.scoped
#uevents= #uevents.after(Time.now)
end
def mailbox
#mailbox ||= current_user.mailbox
end
end
and then all the classes in my web application extends this class :)
I found a way is to use before_filter. by defining the filter in the ApplicationController (so that you can access it from any controller).
class ApplicationController < ActionController::Base
# ..
protected
def load_messages
#messages = Message.all
end
end
and then in the any controller:
class FooController < ApplicationController
before_filter :load_messages
def index
# #messages is set
end
end
I have a question regarding the reuse of code among controller actions. I think it is a fairly standard situation, so I am interested in what's the best practice in Rails.
Let's say I have a films resource with a corresponding FilmsController, which has a nested resource comments served by CommentsController. The nested resource can be rendered on its own using its index and show actions. However, it should also be possible to render the comments embedded in the corresponding film page.
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
1) Force the CommentsController.index to render to a string and then pass it in a variable to the film view?
Or 2) call the CommentsController.index directly in the film view as a kind of "partial", executing the database queries from there?
Or 3) create a separate method in CommentsController responsible for the database handling, call it from both CommentsController.index and FilmsController.show, and use the corresponding view in both the places, too?
To me the options 1) and 2) seem a bit messy, while 3) is not modular and involves some repeating of code. Is there any better way to accomplish this?
Thanks a lot!
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
You could move the shared controller logic into a inside your application controller (or a lib and require it appropriately), a la:
class ApplicationController < ActionController::Base
def foo
#foo = "foo"
end
end
Comments Controller:
class CommentsController < ApplicationController
before_filter :foo, :only => [:index]
def index
end
end
Films Controller:
class FilmsController < ApplicationController
before_filter :foo, :only => [:show]
def show
end
end
For repeated view logic you can move that to a common folder, say your_app/app/views/shared/_foo.html.erb and render that appropriately.
Another option is to place the relevant code into an external module:
lib/mymodule.rb
module MyModule
def foo
end
end
And then you can include the module inside your controller or anywhere you want access to your foo method.
class CommentsController < ApplicationController
include MyModule
def index
foo()
end
end
In the following example,
before_filter :foo
before_filter :bar
before_filter :wah
prepend_before_filter :heehee
prepend_before_filter :haha
so then the execution orders will be:
haha, heehee, foo, bar, wah? <-- note that haha is actually before heehee
And is there a reason not to list haha and heehee first in the first place but actually use prepend?
To my knowledge this is to solve class inheritance where you cannot define the order of the before_filter:
ApplicationController < ActionController::Base
before_filter :do_this_first
#....
end
SomeController < ApplicationController
before_filter :do_this_second
#....
end
Here, neither of the methods defined will have preference unless you use a prepend_before_filter.
Looks like prepend_filter, in a queue of before_filters, it adds a filter in front of the queue. Hence, last come, first served.