Rails Action Cache Read inserts Plain HTML - ruby-on-rails

I've set up simple action caching for the show page of one of my models. Caches are generatedand expires correctly, however, every time a new cache is written and read for the first time, the view inserts 'escaped' HTML into the layout, meaning the web browser displays the actual cache contents rather than rendering the HTML page.
In my Uploads controller, i declared chaches like this:
caches_action :index, :show, layout: false
cache_sweeper :drumiverse_sweeper, only: [:edit, :destroy, :update]
Sweeper Class (which is executed correctly):
class UploadsSweeper < ActionController::Caching::Sweeper
observe Upload
def after_save(record)
puts "Expiring Caches after Saving"
expire_action(controller: '/pages', action: 'home')
expire_action(controller: '/pages', action: 'resources')
expire_action(controller: '/pages', action: 'videos')
expire_fragment('footer')
expire_action(controller:'/uploads',action: 'index')
expire_fragment(record)
end
end
I used the layout: false option because i only want the action view to get cached (not the entire layout, including menu bar).
After reloading the page, the cache is read and inserted correctly and the page renders as it should. It is just the first time after writing a new cache that it actually prints out the cache contents as a HTML string.
Has anyone seen this problem before?

Switching to the master branch of the gem, which incorporates Pull Request 48 (per #steakchaser's recommendation), solved this for me on Heroku production. In the gemfile:
gem 'actionpack-action_caching', git: "https://github.com/rails/actionpack-action_caching.git", branch: "master"
Bundled, committed, and pushed.

Related

How to cache unlimited number of static pages in Rails

I have a Rails app which allows users to create unlimited number of static pages (I store these pages in postgres database), because these pages are statics I would like to use page caching, but I am not sure what's the limit number/size of caching pages in Rails, can I cache unlimited number of pages ?
we do this on our main company blog at https://reinteractive.net.
What you probably want to do is do fragment caching on the show action of the page you want to cache. There are some pitfalls to this, the biggest of which is expiring the cache if the underlying page changes.
It works like this:
In the routes.rb file:
AppName::Application.routes.draw do
get "page/:id" => "pages#show"
end
In the controller (app/controllers/pages_controller.rb):
class PagesController < ApplicationController::Base
def show
#page = Page.find(params[:id])
end
end
Note here in the controller you are hitting the database on every request but this should be a really fast request with the correct index in place.
Then in the view (app/views/pages/show.html.erb):
<% cache("pages/show/#{#page.id}-#{#page.updated_at.to_i}") do %>
<%# Render your complex page here %>
<% end %>
What you gain from caching a fragment like this is that the output of the rendering gets stored in the cache and if this page takes a while to render, you can get significant time savings, especially if it takes 100ms or more to render the page and all it's parts, you pay for it the first time, and then after that it loads in a few milliseconds.
Note we are checking the last update time of the page? This is so that if you want to expire this cache (and render again) all you need to do is call page.touch on the page object to update is updated_at time.
If you have any other objects that could also update (such as say a page.header_image or something, then you can also put the updated_at on these related objects into the cache key as well, or expire them manually when you update the page.
An alternative to adding updated_at into the cache key is expiring them in the model like this:
class Page < ActiveRecord::Base
after_save :expire_cache
def expire_cache
Rails.cache.delete("CACHE_KEY")
end
end
But this has it's own challenges.
Good luck!
#MikelLindsaar 's answer is good but kinda too much work for something that could be handled easier, ActiveRecord objects have a method called cache_key that auto generates a unique key for each record, if the record gets touched or updated, the generated key will be different, this key is used for generating the whole view caching key, which is a function that has a lot of parameters, like the object id, updated_at (from the cache_key), and the view's hash ( the cache gets invalidated if the view file is updated, not just the db), so there's no worry about any stale data, all you need to do would be:
The controller
class PagesController < ApplicationController::Base
def show
#page = Page.find(params[:id])
end
end
The view
<% cache #page do %>
<%# Render your page here %>
<% end %>
if you have multiple pages that use the same page object you could use a compound cache key, something like this for example
<% cache(prefix: 'page/show', page: #page) %>
and rails will handle the generation of the caching key.
More info about this over here http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching

Rendering on specific pages through application.html.erb file?

I have a search box that I only want to render on particular pages through the navigation section in my application.html.erb file.
How do I set exceptions? Is this done through the main application controller?
There are several ways to do this.
Most obvious way is to use an instance variable for flagging.
In your application.html.erb
<%= render 'search' if #search_box %>
And wherever you want to show the search, set the flag instance variable to true.
def show
#search_box = true
...
end
Edit
You might also want to utilize Rails' filters in your controllers if you want multiple actions to show search.
before_action :flag_search_box, :only => [:show, :new, :all_kinds_of_controller_actions_where_i_wanna_show_search]
...
private
def flag_search_box
#search_box = true
end
I might suggest to put the search box not in the application.html page but maybe in the separate html pages that you want it to render on. You could make the search a partial so that you could access it from the other pages with just one line of code.

Execute two restful actions in one page load

I have two models, File and Download. In the same page load, I want to read a File (serve it to the user) and create a Download.
I could either do:
GET /file/:id
PUT /file/:id/download
2 redirected to 1
Is there a correct way of handling such a situation?
I suspect what you need is just an after_filter on your file controller. Something along the lines of:
after_filter :log_download, :only => :show
protected
def log_download
# code to log a download after the show action
end
This is a lot simpler than trying to chain together request for the same effect.
Have your file download link point to a :download action, where you can log the download then render your file:
def download
DownloadLog.create(...)
render :file => ......
end

How can I cache a page manually in RoR?

I am trying to create a site in RoR and have enabled caching for some pages and actions. The related DB may not accessible every time and hence using the cache is very much required. Hence I cant wait for someone to actually visit the page, render it and then cache it. Instead I want whatever is cache-able to be cached manually, programatically. Is it actually possible or is it that caching is completely automatic in RoR?
The lazy* solution would be to visit the page as part of your deployment process with lynx, or even curl. That would trigger the cache event from the outside, but at a time of your choosing.
(*) lazy in a good way, I hope.
Check out this page_cache plugin. Seems like this is what you need.
I am doing manual caching triggering now, and looks like you can use built-in API of actionpack-page_caching plugin to manually trigger the creating of pages cache. You need to use cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) function with attributes (look line 80 at https://github.com/rails/actionpack-page_caching/blob/master/lib/action_controller/caching/pages.rb). Here I made sample action, which is iterating over some collection and making cache of "show" method of each item of this collection.
def precompile
#pages = Page.all
#pages.each do |page|
#page = page
cache_page(render_to_string(template: 'pages/show'), url_for(action: :show, id: #page, only_path: true))
end
redirect_to '/'
end
The url_for(action: :show, id: #page, only_path: true) part of my code is not very clean, but it is the first version of code which is working as I needed, any refactor are welcome.
Also, this code will overwrite the cache file every time it is fired, without checking for any changes or expirations.
Ref :- Link
class ProductsController < ActionController
caches_page :index
def index
end
end
set perform caching to true in your enviorment config/environments/development.rb
config.action_controller.perform_caching = true

How to cache render :json

I have a controller index action which returns json output.
render :json => my_array.to_json
What type of caching do I have to use here. Does 'page caching' make sense for this.
Or do I have to do action caching like below
caches_action :index
Either action caching or page caching would work fine; page caching would have the benefit of never calling the Rails stack, but it depends on whether you need to control who accesses that Json feed.
I'm a big fan of using page caching if you can get away with it - there are big savings on system resources to be had. :)
EDIT: Page caching example, in case there was any confusion:
class SomeController < ApplicationController
caches_page :index
def index
render :json => my_array.to_json
end
end
Unless I've misunderstood something, that should be all you need to do.
Same considerations should apply to JSON as any other output. If you need to validate access to the data for the user, then action caching is the way to go, otherwise page caching should be fine.
If the data changes due to logic in your app, then both forms of caching are problematic and you are better off using something else.

Resources