Rails - Caching all the users with a specific attribute value - ruby-on-rails

In a Rails 6 app, I have a page that shows all the users who are admins.
Since the list changes very seldom I'd like to cache the fragment.
How can I handle such a cache?
I suppose to invalidate it I should have some kind of after_save callback in the user model to check if the saved user just became an admin or he's not anymore.
Any ideas?

No need for callbacks. You can set cache key, so every time there is new admin, key will change and new cache will be generated. For example:
<% cache "admin-list-#{#admins.count}" do %>
<% #admins.each do |admin| %>
<%= admin.name %>
<% end %>
<% end %>
In case when admin details will change and number of admin users will be the same, you can use collection caching:
https://guides.rubyonrails.org/caching_with_rails.html#collection-caching
<%= render partial: 'users/admin', collection: #admins, cached: true %>
To test caching in development use this command:
rails dev:cache

Related

Ruby on Rails - Session variable on form submit

I have an application which requires visitors to fill out a form and then it redirects to a second page. The client does not want to require visitors sign up to view this page, only to fill out the form.
The way I am attempting to do this is by creating a session variable when the page is visited and then checking to see if the variable exists before the next page is accessible. Is it possible to delay the creation of the session variable until the submit action is processed? If so what would that look like?
Also, can anyone think of a better way to do this? Sorry, this is probably a dumb question.
The session cookie would be declared after the first submit.
I presume the first submit will load up a controller#action in which you'll then redirect to the next page. Just set the session in there:
#app/views/forms/1.html.erb
<%= form_tag form_1_submit_path do %>
...
<% end %>
This will allow you to do the following:
#app/controllers/forms_controller.rb
class FormsController < ApplicationController
def form_1_submit
session[:value] = params[:value]
redirect_to form_2
end
end
Thus you'll have session[:value] all set and ready to use on the next form:
#app/views/forms/2.html.erb
<%= form_tag .... do %>
<%= text_field_tag :test, value: session[:value] %>
<% end %>

rails_digest caching per user

I my app, my views are generated according to users actions and rights.
I would like to implement rails_digest to cache my pages but I need to do this per user.
I know it's possible in fragment cache:
<% cache "mypage", project, user %>
But this doesn't seem to work in rails_digest.
Any clue?
I found the way to do it:
just write: <% cache [user, project] do %> ... <% end %>
When something changes in the project, all you need to do is project.touch. The cache will be reset for everyone.

Rails - How to exclude blocks of code from fragment cache

I'm using fragment cache but i have inline code that is user specific like:
<% cache #page do %>
stuff here
<% if current_user %>
user specific
<% end %>
more here
<% end %>
So i want to exclude the several blocks of code that are user specific. Is there a way to do that in Rails or should i make an if statement in the beginning and make different caches for logged users and regular visitors? (i will have major duplication of code this way).
For per-user fragments, you can put models in array an array:
<% cache [#page, current_user] do %>
Rails will make a cache-key out of them, like:
pages/page_id-page_timestamp/users/user_id-user_timestamp
This way your fragments will be invalidated on a user/page update since the time-stamps are coming from their updated_at (see cache_key for details).

Caching particular partial rails 3.0.x

I have a home page which has some partials rendered all over the page.And it also has session header as well(login).
Partial contains set of books paginated. Now I want to cache this partial as it is getting updated once in a week.
Question 1 : How do I cache that particular partial (Without hitting
db) ?
Question 2 : How do I delete(expire) cached content when I update
that books model ?
You're looking for fragment caching here, which occurs on the view layer. Fragment caching and expiration of stored contents is surprisingly easy to do. You have a list of books, so let's say your view looks a bit like this:
<ul>
<% #books.each do |book| %>
<li><%= book.name %></li>
<% end %>
</ul>
To enable caching for just this bit, simply wrap it in cache:
<% cache do %>
<ul>
<% #books.each do |book| %>
<li><%= book.name %></li>
<% end %>
</ul>
<% end %>
Of course, this doesn't name the cache or do anything really special with it... while Rails will auto-select a unique name for this cache fragment, it won't be really helpful. We can do better. Let's use DHH's key-based cache expiration technique and give the cache a name relating to its content.
<% cache ['book-list', *#books] do %>
<ul>
<% #books.each do |book| %>
<li><%= book.name %></li>
<% end %>
</ul>
<% end %>
Passing arguments into cache builds the cache key from the supplied arguments. Strings are passed in directly -- so, here, the cache will always be prefaced with 'book-list'. This is to prevent cache collisions with other places you might be caching the same content, but with a different view. For each member of the #books array, Rails will call cache_key: for ActiveRecord objects, this yields a string composed of its model, ID, and crucially, the last time the object was updated.
This means that when you update the object, the cache key for this fragment will change. In other words, it's automatically getting expired -- when a book is updated, this cache statement will search for a nonexistent key, conclude it doesn't exist, and populate it with new content. Old, stale content will linger in your cache store until evicted by memory or age constraints (memcached does this automatically).
I use this technique in a number of production applications and it works wonderfully. For more information, check out that 37signals post, and for general caching information in Rails, see the Ruby on Rails caching guide.
"There are only two hard problems in Computer Science:
cache invalidation and naming things."
-- Phil Karlton
Caching
The Rails Guide to caching is probably always a good entry point for the built in caching strategies rails has to offer. Anyway here comes my very easy approach to caching
# _partial.html.erb
<% cache(some_key, :expires_in => 1.week) do %>
<%# ... content %>
<% end %>
Now some some_key can be any string as long as it is unique. But then again lets try to be a bit more clever about it and make the key somehow dependent on the list of books. Say you actually pass in the array of books some query returned then rails calls cache_key on each of its entries and eventually constructs a unique key for this collection. So when the collection changes the key changes. Thats because cache_key is implemented in ActiveRecord::Base and thus available on all Models. And further more it even uses the timestamps if available.
But then again this will hit the db every time a request is made.
The same in code:
# controller_method
#weekly_books = Books.where 'condition'
# _partial.html.erb
<% cache(#weekly_books) do %>
<%# ... content %>
<% end %>
To avoid hitting the db to often you can also cache the query its self by wrapping the call:
# Book.rb
def self.weeklies
Rails.cache.fetch("book_weeklies", :expires_in => 1.day) do
Books.where 'condition'
end
end
# controller_method
#weekly_books = Books.weeklies
# _partial.html.erb
<% cache(#weekly_books) do %>
<%# ... content %>
<% end %>

Cache only the main content in rails

Using Rails 3.1.1 and Heroku.
I believe this should be a fairly easy fix but I cannot find (and easily verify) how to do this. I have a very slow controller (6 sec) Product#show, with lots of N+1 and other things I will have to solve.
The website is a two-column website (main-column and right-column) where the main content from Product#show is shown in one column and daily product are shown in the other, including a "Random Product from the Database".
What I want to do is to let the content in main-column that is created by Product#show be cached (and thus bypass the controller and win 6 seconds). I do, however, want the right column to be dynamic (and loaded for each page request).
If I use caches_page :show it will cache the entire website, including the right-column, which makes me have to expire the cache every day in order to be able to load a new Daily Product. Not a good solution.
If I use cache('product-show' + #product.slug) do it only caches the view (right?) and still have to go through the controller.
So, how can I solve this?
You can achieve this with fragment caching like below:
def show
if !fragment_exist?("main_content")
#products = Product.all
#users_count = User.count
end
#random_products = Product.order("RANDOM()").limit(10)
end
show.html.erb
<!--MAIN CONTENT-->
<% cache("main_content") do %>
<%= #users_count %>
<% #products.each do |product| %>
<%= product.name %>
<% end %>
<% end %>
<!--SIDE CONTENT-->
<% #random_products.each do %>
<%= product.name %>
<% end %>
Use fragment caching, and don't load things in the controller.
If you have a very complex query, let it live in the controller as a scope, and only evaluate it in the view.
If you have a complex process to do so the query must be executed, use a helper method.
If you manage to just load lazy queries in the controller, if the cache is hit none of them will be executed.

Resources