I am following along with the 'Agile Web Development with Rails 4' guide and have come to a section on caching. Here are the steps to follow:
in config/environments/development.rb
config.action_controller.perform_caching = true
In app/models/product.rb
def self.latest
Product.order(:updated_at).last
end
in views/store/index.html.erb
<% cache ['store', Product.latest] do %>
<% #products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>
<% end %>
<% end %>
<% end %>
To verify the cash is working, the book says: "As far as verifying that this works, unfortunately there isn't much to see. If you go to that page, you should see nothing change, which in fact is the point! The best you can do is to make a change to the template anywhere inside the cache block without updating any product and verifying that you do not see that update because the cached version of the page has not been updated".
However, when I try and add a "Hello" string in the code within the cache blocks, it appears on the page. I've done all the server restarting and what not.
But, when I reload the page on my local host I do see this line
Cache digest for app/views/store/index.html.erb: 6c620ede1d4e824439a7b0e3b177620f
which is not their when I have
config.action_controller.perform_caching = false
Link to git hub repo: https://github.com/BrianLobdell/depot
Thank you,
Brian
Rails updates the cache when changing the file, so it's easier to manipulate the cache instead.
You can retrieve the cache fragment using the cache digest for the page in your Rails console (the digest might have changed, make sure to use the recent one!):
key = ['store', Product.latest, '6c620ede1d4e824439a7b0e3b177620f']
ActionController::Base.new.read_fragment(key)
This should return the cached fragment. To ensure that Ruby actually hits the cache when serving the page, you could replace the fragment with some other content:
ActionController::Base.new.write_fragment(key, 'it works!')
Reload the page, you should see "it works!".
Related
I updated Rails to 5.2 version with ActiveStorage
Everything works fine, but when I perform a loop through my model attached files in order to display them as bootstrap cards, I see my files attributes on the page.
I don't want it to display anything.
How can I prevent that?
<strong style="margin: 10px">Files :</strong>
<div class="card-columns">
<%= #mymodel.files.each do |file| %>
<% end %>
</div>
what it makes on my page
Remove the = from your loop header...
Instead of
<%= #mymodel.files.each do |file| %>
Use
<% #mymodel.files.each do |file| %>
Checkout what the difference is
In my rails 4 app I'm trying to take off with caching, but I'm a bit confused thanks to the different versions of cache-key-settings, cache helpers and auto-expiration.
So let me ask this through few examples. I don't move the examples to different questions on purpose since I feel this way anybody can understand the subtle differences at one glance.
1: latest users in sidebar
I'd like to display the latest users. This of course is the same for all the users in the app and displayed on all the pages. In the railscasts I saw a similar example where it got expired by calling expire_fragment... in a controller. But according to other resources this should expire automatically when something changes (e.g. new user registration). So my question: Am I setting the key properly and will it auto-expire?
_sidebar.html.erb (displayed on all pages in sidebar)
<% cache 'latest-users' %>
<%= render 'users/user_sidebar' %>
<% end %>
_users_sidebar.html.erb
<% #profiles_sidebar.each do |profile| %>
<%= profile.full_name %>
........
<% end %>
2: product show page
I'd like to display a given product (only on show page). This is the same again for all the users, but there are more versions since there are more products. The question is the same again: Am I setting the key properly and will it auto-expire?
products/show.html.erb
<% cache #product %>
<%= #product.name.upcase %>
<%= #product.user.full_name %>
<% end %>
3: products/index (paginated with will-paginate gem)
Here I'd like to cache all the products on a given page of the pagination at once, so products get cached in blocks. This is also the same for all the users, and only gets displayed on the products index page. (Later on I'd like to implement the russian-doll-caching for the individual products on this page.) My question: Am I doing this right and will it auto-expire?
products index.html.erb
<% cache [#products, params[:page]] %>
<%= render #products %>
<% end %>
_product.html.erb
<%= product.name %>
<%= product.user.full_name %>
.....
Example code I tried to use (not sure if it's a good one):
First with index page and with no russian doll.
Second is with russian doll for the show page with comments.
There is a pretty big difference between caching a single record and a collection of records.
You can quite simply tell if a single record has been changed by looking at the timestamp. The default cache_key method works like this:
Product.new.cache_key # => "products/new" - never expires
Product.find(5).cache_key # => "products/5" (updated_at not available)
Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
However telling when a collection is stale depends very much on how it is used.
In your first example you only really care about the created_at timestamp - in other situations you might want to look at when records are updated or even when associated records have been inserted / updated. There is no right answer here.
1.
First you would pull N number of users ordered by created_at:
#noobs = User.order(created_at: :desc).limit(10)
We can simply invalidate the cache here by looking at the first user in the collection.
<!-- _sidebar.html.erb -->
<% cache(#noobs.first) do %>
<%= render partial: 'users/profile', collection: #noobs %>
<% end %>
We can do this since we know that if a new user is registered (s)he will bump the previous number one down a slot.
We can then cache each individual user with russian doll caching. Notice that since the partial is called profile the partial gets passed the local variable profile:
<!-- users/_profile.html.erb -->
<% cache(profile) do %>
<%= profile.full_name %>
<% end %>
2.
Yes. It will expire. You might want to a partial with russian doll caching like the other examples.
3.
For a paginated collection you can use the ids of the records in the array to create a cache key.
Edited.
Since you don't want serve a stale representation of a product that may be updated you would also want to use updated_at as a cache key in the "outer layer" of the russian doll.
In this case it makes sense to load the records entirely. You can ignore my previous comment about .ids.
products/index.html.erb:
<% cache([#products.map(&:id), #products.map(&:updated_at).max]) do %>
<%= render #products %>
<% end %>
products/_product.html.erb:
<% cache(product) do %>
<%= product.name %>
<% end %>
IMHO, cache key is the spirit of the whole concept.
Now let's discuss these examples.
latest users in sidebar: fixed string as cache key
cache_key could be views/latest-users/7a1156131a6928cb0026877f8b749ac9 which the digest 7a11561.. is MD5 of cache block literal.
In this case, the cache only expires when you change the template or anything in this block.
<% cache 'latest-users' do %>
<%= render 'users/user_sidebar' %>
<% end %>
product show page: object as cache key
cache_key could be views/product/123-20160330214154/9be3eace37fa886b0816f107b581ed67, notice the cache is namespace with #{product.to_s}/#{product.id}-#{product.updated_at}.
In this case, the cache expires when (1) product.updated_at changed or (2) cache block literal changed.
And notice the cache differs from different product by id.
<% cache #product %>
<%= #product.name.upcase %>
<%= #product.user.full_name %>
<% end %>
products/index (paginated with will-paginate gem): array as cache key
cache_key could be views/product/123-20160330214154/product/456-20160330214154/d5f56b3fdb0dbaf184cc7ff72208195e not sure about this. but anyway, it should be expand something like this.
In this case, the cache expires when (1) either product-123 or product-456 changed(product.updated_at changed) or (2) cache block literal changed.
And the cache differs from different content of #products by their ids, so there's no need to append params[:page], it will cache each different page because of their different #products content.
<% cache [#products, params[:page]] %>
<%= render #products %>
<% end %>
You could read the article written by DHH, it described Russian Doll Caching very well. And the API doc
Following the guide from "Agile Web Development with Rails 4."
It covers caching for a product catalog to only re-render products that changed.
I edited (config/environments/development.rb):
config.action_controller.perform_caching = true
Added code to return most recently updated product:
(app/models/product.rb)
def self.latest
Product.order(:updated_at).latest
end
Lastly updated the store index for the cache:
(app/views/store/index.html.erb)
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% #products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>
<% end %>
<% end %>
<% end %>
The book states the only way to test if caching is working is to change the view so all I did was add some lorem ipsum within one or both cache tags but the browser immediately shows the change on refresh... Frustrating! Any clue?
Your cache key for the collection is not changing. That happens because cache ['store', Product.latest] is not as clever as you expect. If you have a look at the log you'll see that the cache key that was used is a literal including something like ActiveRecord::Relation::Products.
Use the last updated_at value cache ['store', Product.latest.max(:updated_at)] for better results.
In Rails 5 this will be easier due to the addition of cache_key to ActiveRecord::Relation a few weeks ago.
try using
<% cache ['v1', Product.order(updated_at: :desc).last] do %>
instead of
<% cache ['store', Product.latest] do %>
I'm working my way through the book Agile Web Development with Rails4, and I just read the (first) part about caching parts of a view to avoid overwhelming the database.
I've of course set the caching option to true in the config for the development environment.
The problem is that caching doesn't seem to be working.
Here is my app/views/store/index.html.erb file, exactly like the one given in the book, to enable caching :
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% #products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>
<% end %>
<% end %>
<% end %>
And here is the rails server log, clearly showing that the databse was accessed multiple times (although a line mentions caching) : http://pastebin.com/v2jGiHKL
Here is my app/views/store/index.html.erb file, where I tried something else for caching :
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache('caching') do %>
<% #products.each do |product| %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>
<% end %>
<% end %>
And here is the corresponding log, showing that caching was successful (as the database was not queried) : http://pastebin.com/ZTk9A9RA
Can someone explain why one seems to work and not the other, or how the first one should work ? Thank you :)
Note that in the book, it says that with caching enabled, reloading the store page shouldn't show new parts of the store/index.html.erb if the changes were made inside a cached block ; yet it does in both case. Any idea ?
The parameter you pass to the cache api is the caching key. The hashing function of that key is where the cached fragment will be stored, and where it will be looked for.
In your code (cache('caching')) the caching key is hard-coded, and never changes, so, unless the cache is invalidated (either manually in your code, or when its TTL has passed) - the same fragment will be sent to the client.
In the code from the book (cache ['store', Product.latest]) the key depends on the latest product. This means that if the latest product changes (another product was added, or updated) - the next call will automatically 'know' not to take the page from the cache, but to recreate the cache.
What is shown in the logs:
[1m[35mProduct Load (0.3ms)[0m SELECT "products".* FROM "products" ORDER BY "products"."updated_at" DESC LIMIT 1
Is actually the result of Product.latest, not of Product.all.
To sum up - both code fragments are cached, but in the example code, there is a (small) hit to the database to verify the validity of the cache, whether the page was cached or not.
If you are invalidating the cache yourself, you can keep the hard-coded version of the code, but if not, you better think of an invalidation scheme, whether it is like the example code suggests or something else.
I'm following the 'Agile Web Development with Rails 4' book and I'm stuck at page 105 about the caching.
I have the following code in index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% #products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>
<% end %>
<% end %>
<% end %>
The first doubt is the following:
1) What does cache ['store', Product.latest] exactly do? It creates a cache, available from all StoreController actions, named "store" and associates the cache with the Product.latest: why should I do the last thing? Why do I need to associate my cache to a Product.latest?
Always at the same page the book says: "As far as verifying that this works, unfortunately there isn't much to see. If you go to that page, you should see nothing change, which in fact is the point! The best you can do is to make a change to the template anywhere inside the cache block without updating any product and verifying that you do not see that update because the cached version of the page has not been updated".
So I tried something like this:
<% cache ['store', Product.latest] do %>
"hello"
........
........
<% end %>
But I still get this update, the page shows me the "hello" string, why is it so? Shouldn't I see it?
P.S. Obviously I edited my config/environments/development.rb and restarted the server
Start your application on production mode and you will see that caching works.
What did you edited in development.rb? Please compare it to production.rb and copy-paste all caching configuration properties.
I had the same "problem", and I think what the book is saying is not correct. As far as I can tell, the reason why you see the changes on the website, even though caching is active, is that Rails 4 uses cache digests. For more information, check out:
http://blog.remarkablelabs.com/2012/12/russian-doll-caching-cache-digests-rails-4-countdown-to-2013
When you changed config.cache_classes to false, you just told Rails to not automatically regenerate views with each request. That's why you stopped seeing changes, it's not because "caching" was working correctly.