Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cache a personalised fragment in Rails 4?

My app (Rails 4) allows users to vote on posts. Is it possible to cache the post, but personalise the vote cache so it shows one personalised for current_user? For example, whether the user voted or not.

I would rather not change the html structure to achieve this.

# posts/_post.html.slim
- cache post do
  h1 = post.title
  = post.text
  = render 'votes/form', post: post

# votes/_form.html.slim
- if signed_in? && current_user.voted?(post)
  = form_for current_user.votes.find_by(post: post), method: :delete do |f|
    = f.submit
- else
  = form_for Vote.new do |f|
    = f.submit
like image 892
dee Avatar asked Jun 17 '13 18:06

dee


People also ask

How can you implement caching in Rails?

In order to use page and action caching you will need to add actionpack-page_caching and actionpack-action_caching to your Gemfile . By default, caching is only enabled in your production environment. You can play around with caching locally by running rails dev:cache , or by setting config. action_controller.

How do I use Redis cache Rails?

To use Redis as a Rails cache store, use a dedicated cache instance that's set up as an LRU (Last Recently Used) cache instead of pointing the store at your existing Redis server, to make sure entries are dropped from the store when it reaches its maximum size.

What is Cache sweeper in Rails?

Cache sweeping is a mechanism which allows you to get around having a ton of expire_{page,action,fragment} calls in your code. It does this by moving all the work required to expire cached content into na ActionController::Caching::Sweeper class.

What are view caches?

View caching in Ruby on Rails is taking the HTML that a view generates and storing it for later.


2 Answers

You have two options here:

Option 1: don't cache the votes

This is the simplest solution and the one i personally recommends. You just don't cache the dynamic user dependent part so you have something like this:

# posts/_post.html.slim
- cache post do
  h1 = post.title
  = post.text
= render 'votes/form', post: post # not cached

Option 2: use javascript

This solution is more complex, but is actually how basecamp do it (however mostly with more simple examples). You have both part rendered on the page but remove one of them with javascript. Here is a example using jQuery and CoffeeScript:

# posts/_post.html.slim
- cache post do
  h1 = post.title
  = post.text
  = render 'votes/form', post: post

# votes/_form.html.slim
div#votes{"data-id" => post.id}
  .not_voted
    = form_for current_user.votes.find_by(post: post), method: :delete do |f|
      = f.submit
  .voted
    = form_for Vote.new do |f|
      = f.submit

# css
.not_voted {
  display:none;
}

# javascript (coffeescript)
jQuery ->
  if $('#votes').length
    $.getScript('/posts/current/' + $('#votes').data('id'))

# posts_controller.b
def current
  @post = Post.find(params[:id])
end

# users/current.js.erb
<% signed_in? && current_user.voted?(@post) %>
  $('.voted').hide();
  $('.not_voted').show();
<% end %>

I would however properly change voted? method to accept a id, so you don't need to make a new query. You can learn more about this approach in this railscasts: http://railscasts.com/episodes/169-dynamic-page-caching-revised?view=asciicast

like image 54
jokklan Avatar answered Oct 17 '22 17:10

jokklan


Try the following, this will create 2 different fragments for both voted and not voted for each post. It will be read by according to its status.

# posts/_post.html.slim
- cache [post, current_user.votes.find_by(post: post)]do
  h1 = post.title
  = post.text
  = render 'votes/form', post: post
like image 29
beydogan Avatar answered Oct 17 '22 15:10

beydogan