Ryan: Thanks for another GREAT 'cast. You inspire me to write great code.
Please help to understand: I thought database calls were meant to be in the controller. I understand moving logic to the model, but I thought it was more appropriate to keep calls out of the view and in the controller. Am I wrong here or is best to keep calls in the controller unless the fragment is cached?
@Bryce, many developers don't like calling model finds directly from the view, but I usually don't have a problem with it. IMO views have as much access to models as controllers do in MVC.
But then you could say what's stopping the view from taking over the controllers role and doing all model processing? It's important to understand the role of a controller.
Don't think of the controller as a layer between the view and the model, instead think of it as a layer between the user and the application. The controller should process user input and filter that to the models and views. Many times this involves taking the params hash and fetching models for the view.
Notice in this case the @recent_products array had nothing to do with user input. Therefore it belongs in the view more than the controller. Why should the controller care that we want to display the recent products in addition to what the user requested?
I would have a much harder time moving the @product find itself into the view because this comes from the user input (params hash) and is directly what the user requested.
This is all my own theory. Other developers think differently so I encourage you to find what works best for you.
@chris, great question. Unfortunately I don't know the answer as I don't test caching. I imagine you will have to enable caching temporarily in your testing environment and check for the existance of the file. Alternatively you could use mocking and just make sure youre view calls the cache method with the proper parameters.
Great screencast - things worked without a flaw on my development machine and the cached partials are stored in /tmp/cache directory. I noticed, however, on the production machine that neither the cached partials nor the /tmp/cache directory exist - am I missing something or are my partials not getting cached? Thanks.
p.s. I have this line in environments/production.rb: config.action_controller.perform_caching = true so I believe caching is turned on.
@sthapit, there are various places the cache could be stored. In development mode this defaults to a file store, but in production it defaults to a memory store. If you have a mongrel cluster then memory store probably isn't the best because it will need to be cached separately for each mongrel process and memory could get out of hand. Instead I recommend starting with a file based store by adding this line to your production.rb file:
that makes perfect sense. yes, i'm using a mongrel cluster so i added that to production.rb and /tmp/cache now stores all the cached partials.
i'd also noticed that without that line the cached partial wouldn't expire with the sweeper consistently - but i think it makes sense now. it was probably not getting refreshed on all mongrels. but now that problem is gone as well. thanks!
nice screencast!!! I have a small question about the cache_sweeper. What i do, if i have more controllers, or conjobs to modify the product model. What is the best way to handle this fact?
Excellent screencast... however, I am facing a little problem here.
I've got a messages controller (and a message Model), an different methods among which index, unread, flagged... etc that performs different Message.find(...) but render messages in the same way, with a "respond_to", for each type of call (HTL, XML, or AJAX).
Should I put all the logic in the partial, within the cache block? It seems pretty awkward to do a case params[:action] in my view... and keeping index unread, flagged actions empty in my controller!
In my project, there are many accounts. Each account has many users, but the fragments can be cached for all the users in an account, so putting the account id in the cache identifier is enough to make them unique.
So you could do
<% cache "recent_contacts_#{@account.id}" do %>
<%= render :partial => "/shared/contact", :collection => @contacts %>
<% end %>
Very nice presentation (on a subject that usually is so soporific). I had only a doubt on the view issuing a call to the model (that I had never seen in Rails).
I then saw the answer given by Ryan (post #10): "the @recent_products array had nothing to do with user input. Therefore it belongs in the view more than the controller. Why should the controller care that we want to display the recent products in addition to what the user requested?".
What's the best way to cache the individual records in a render like this:
render :xml => @products.to_xml
I'm caching the entire render with action caching, but when I expire the action, I shouldn't have to re-render each individual product's XML, possibly including deep associations. Is there a way without throwing it all into the views and fragment caching there?
This subject is one of the most important if you plan to have a dynamic and responsive web site.
Most of programmers come to this point late in the development process.
As usual Ryan is right on the spot, so thanks once again.
What about:
controller:
unless read_fragment("recent_products")
@product = Product.find_recent
end
Link is down for me:
http://media.railscasts.com/videos/090_fragment_caching.mov
@heip, that would work too. I still prefer to place the find in the view, but it's subjective.
@Michel, it's working for me. Is it still down for you?
Ryan: Thanks for another GREAT 'cast. You inspire me to write great code.
Please help to understand: I thought database calls were meant to be in the controller. I understand moving logic to the model, but I thought it was more appropriate to keep calls out of the view and in the controller. Am I wrong here or is best to keep calls in the controller unless the fragment is cached?
Thanks again for your great screen casts.
Bryce
Another great screencast.
I still have a question, though. Is caching something I should test? How would I do this?
You hit another one out of the park with this one. Simple, concise, easy to follow.
I Like.
@Bryce, many developers don't like calling model finds directly from the view, but I usually don't have a problem with it. IMO views have as much access to models as controllers do in MVC.
But then you could say what's stopping the view from taking over the controllers role and doing all model processing? It's important to understand the role of a controller.
Don't think of the controller as a layer between the view and the model, instead think of it as a layer between the user and the application. The controller should process user input and filter that to the models and views. Many times this involves taking the params hash and fetching models for the view.
Notice in this case the @recent_products array had nothing to do with user input. Therefore it belongs in the view more than the controller. Why should the controller care that we want to display the recent products in addition to what the user requested?
I would have a much harder time moving the @product find itself into the view because this comes from the user input (params hash) and is directly what the user requested.
This is all my own theory. Other developers think differently so I encourage you to find what works best for you.
@chris, great question. Unfortunately I don't know the answer as I don't test caching. I imagine you will have to enable caching temporarily in your testing environment and check for the existance of the file. Alternatively you could use mocking and just make sure youre view calls the cache method with the proper parameters.
Great screencast - things worked without a flaw on my development machine and the cached partials are stored in /tmp/cache directory. I noticed, however, on the production machine that neither the cached partials nor the /tmp/cache directory exist - am I missing something or are my partials not getting cached? Thanks.
p.s. I have this line in environments/production.rb: config.action_controller.perform_caching = true so I believe caching is turned on.
/current/tmp: ls only shows
pids
Ah the link is now working for me!
Thanks for the great screencasts!
@sthapit, there are various places the cache could be stored. In development mode this defaults to a file store, but in production it defaults to a memory store. If you have a mongrel cluster then memory store probably isn't the best because it will need to be cached separately for each mongrel process and memory could get out of hand. Instead I recommend starting with a file based store by adding this line to your production.rb file:
config.action_controller.fragment_cache_store = [:file_store, "#{RAILS_ROOT}/tmp/cache"]
that makes perfect sense. yes, i'm using a mongrel cluster so i added that to production.rb and /tmp/cache now stores all the cached partials.
i'd also noticed that without that line the cached partial wouldn't expire with the sweeper consistently - but i think it makes sense now. it was probably not getting refreshed on all mongrels. but now that problem is gone as well. thanks!
Where did you get the sweeper snippet for TextMate? Couldn't find it in the RoR bundle.
@Quint, the sweeper snippet is custom. You can find it here.
http://pastie.caboo.se/144239
Exactly what my app needed. Thanks
Hello,
nice screencast!!! I have a small question about the cache_sweeper. What i do, if i have more controllers, or conjobs to modify the product model. What is the best way to handle this fact?
Michael
Excellent screencast... however, I am facing a little problem here.
I've got a messages controller (and a message Model), an different methods among which index, unread, flagged... etc that performs different Message.find(...) but render messages in the same way, with a "respond_to", for each type of call (HTL, XML, or AJAX).
Should I put all the logic in the partial, within the cache block? It seems pretty awkward to do a case params[:action] in my view... and keeping index unread, flagged actions empty in my controller!
What would you do?
Oh, and I forgot to say that this :
unless read_fragment(...)
@collection = Message.find(...)
end
approach deosn't do it for me... since, it messes up the respond_to.
In my project, there are many accounts. Each account has many users, but the fragments can be cached for all the users in an account, so putting the account id in the cache identifier is enough to make them unique.
So you could do
<% cache "recent_contacts_#{@account.id}" do %>
<%= render :partial => "/shared/contact", :collection => @contacts %>
<% end %>
The sweepers folder needs to be under the app folder, in case anyone's wondering and doesn't have time to watch the 'cast. Thanks to the authors.
Very nice presentation (on a subject that usually is so soporific). I had only a doubt on the view issuing a call to the model (that I had never seen in Rails).
I then saw the answer given by Ryan (post #10): "the @recent_products array had nothing to do with user input. Therefore it belongs in the view more than the controller. Why should the controller care that we want to display the recent products in addition to what the user requested?".
Irreverent and beautiful!
Quick question: If the sweeper is observing the model (like an observer), why is a controller declaration required at all?
What's the best way to cache the individual records in a render like this:
render :xml => @products.to_xml
I'm caching the entire render with action caching, but when I expire the action, I shouldn't have to re-render each individual product's XML, possibly including deep associations. Is there a way without throwing it all into the views and fragment caching there?
Caching the whole of is has always proven to be the best option that can be https://flix-account.com