How can I cache a page manually in RoR? - ruby-on-rails

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

Related

Rails Action Cache Read inserts Plain HTML

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.

button to save current page in rails 3.2

I need to have a button to save the current web site (just like clicking on "Save as"), I created a method in the controller which works great for any external site (like http://www.google.com) but doesn't work for the sites inside my application, I get a timeout error!. This has no explanation to me :(
Any clue what is the issue?
#CONTROLLER FILE
def save_current_page
# => Using MECHANIZE
agent = Mechanize.new
page = agent.get request.referer
send_data(page.content, :filename => "filename.txt")
end
I tried also Open URI, same problem!
#CONTROLLER FILE
def save_current_page
# => USANDO OPEN URI
send_data(open(request.referer).read, :filename => "filename.txt")
end
I'm using rails 3.2 and ruby 1.9, any help is appreciated, I already spent like 10 hours trying to make it work!!
Rails can only handle one request at a time. It's a never-ending standoff between the two requests - the first request is waiting for the second request, but the second request is waiting for the first request, and therefore you get a Timeout error. Even if you're running multiple instances of the app with Passenger or something, it's a bad idea.
The only way I can think to get around it would be to use conditional statements like so:
referer = URI.parse(request.referer)
if Rails.application.config.default_url_options[:host] == referer.host
content = "via yoursite.com"
else
agent = Mechanize.new
page = agent.get request.referer
content = page.content
end
send_data content, filename: "filename.txt"
A little dirty but it should get around the Timeout problem. As far a getting the actual content of a page from your own site - that's up to you. You could either render the template, grab something from cache, or just ignore it.
A much better solution would be to enqueue this code into something like Resque or Delayed Job. Then the queue could make the request and wait in line to request the page like normal. It would also mean that the user wouldn't have to wait while your application make a remote request, which is dangerous because who knows how long the page will take to respond.
After several hours and lots of other posts I got to a final solution:
Bricker is right in that it is not possible for rails to render more than once in a call, as taken from http://guides.rubyonrails.org/layouts_and_rendering.html "Can only render or redirect once per action"
The site also states "The rule is that if you do not explicitly render something at the end of a controller action, Rails will automatically look for the action_name.html.erb template in the controller’s view path and render it."
Then, the solution that worked great for me was to tell the controller to render to a string if a download flag (download=true) was set in :params (I also use request.url to have it working from any view in my application)
View:
= link_to 'Download', request.url+"&downloadexcel=true", :class => 'btn btn-primary btn-block'
Controller:
def acontrolleraction
#some controller code here
if params[:downloadexcel]
save_page_xls
else
# render normally
end
end
def save_page_xls
#TRESCLOUD - we create a proper name for the file
path = URI(request.referer).path.gsub(/[^0-9a-z]/i, '')
query = URI(request.referer).query.gsub(/[^0-9a-z]/i, '')
filename = #project_data['NOMBRE']+"_"+path+"_"+query+".xls"
#TRESCLOUD - we render the page into a variable and process it
page = render_to_string
#TRESCLOUD - we send the file for download!
send_data(page, :filename => filename, :type => "application/xls")
end
Thanks for your tips!

Rails, How to render a view/partial in a model

In my model I have:
after_create :push_create
I push_create I need to render a view. I'm trying to do that like so:
def push_event(event_type)
X["XXXXX-#{Rails.env}"].trigger(event_type,
{
:content => render( :partial =>"feeds/feed_item", :locals => { :feed_item => self })
}
)
end
This angers rails as it doesn't like me rendering a view in the model but I need it there.
Error:
NoMethodError (undefined method `render' for #<WallFeed:0x1039be070>):
Suggestions? Should I render it somewhere else somehow? Or how can I render in the model to set content? Thanks
proper solution
Well, "they" are right. You really have to do the rendering in a controller -
but it's fair game to call that controller from a model! Fortunately, AbstractController
in Rails 3 makes it easier than I thought. I wound up making a simple
ActionPusher class, working just like ActionMailer. Perhaps I'll get ambitious and
make this a proper gem someday, but this should serve as a good start for anyone else in my shoes.
I got the most help from this link: http://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/
in lib/action_pusher.rb
class ActionPusher < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
include Rails.application.routes.url_helpers
helper ApplicationHelper
self.view_paths = "app/views"
class Pushable
def initialize(channel, pushtext)
#channel = channel
#pushtext = pushtext
end
def push
Pusher[#channel].trigger('rjs_push', #pushtext )
end
end
end
in app/pushers/users_pusher.rb. I guess the require could go somewhere more global?
require 'action_pusher'
class UsersPusher < ActionPusher
def initialize(user)
#user = user
end
def channel
#user.pusher_key
end
def add_notice(notice = nil)
#notice = notice
Pushable.new channel, render(template: 'users_pusher/add_notice')
end
end
Now in my model, I can just do this:
after_commit :push_add_notice
private
def push_add_notice
UsersPusher.new(user).add_notice(self).push
end
and then you'll want a partial, e.g. app/views/users_pusher/add_notice.js.haml, which could be as simple as:
alert('#{#notice.body}')
I guess you don't really need to do it with Pushable inner class and the .push
call at the end, but I wanted to make it look like ActiveMailer. I also have a
pusher_key method on my user model, to make a channel for each user - but this
is my first day with anything like Pusher, so I can't say for sure if that's the right
strategy. There's more to be fleshed out, but this is enough for me to get started.
Good luck!
(this was my first draft answer, leaving it in because it might help someone)
I've got the general outline of a solution working. Like this, in your model:
after_create :push_new_message
private
def render_anywhere(partial, assigns = {})
view = ActionView::Base.new(ActionController::Base.view_paths, assigns)
view.extend ApplicationHelper
view.render(:partial => partial)
end
def push_new_message
pushstring = render_anywhere('notices/push_new_message', :message_text => self.body)
Pusher[user.pusher_key].trigger!('new_message', pushstring)
end
that is definitely working - the template is rendering, and gets eval()'ed on the client side successfully. I'm planning to clean it up, almost certainly move render_anywhere somewhere more general, and probably try something like this
I can see that pushes will need their own templates, calling the generally available ones, and I may try to collect them all in one place. One nice little problem is that I sometimes use controller_name in my partials, like to light up a menu item, but I'll obviously have to take a different tactic there. I'm guessing I might have to do something to get more helpers available, but I haven't gotten there yet.
Success! Hooray! This should answer your question, and mine - I'll add more detail if it seems appropriate later. Good luck!!!!
original non-answer from an hour ago left for clarity
I don't have an answer, but this timely question deserves more clarification, and I'm hoping to get closer to my answer by helping ask :)
I'm facing the same problem. To explain a little more clearly, Pusher asynchronously sends content to a connected user browser. A typical use case would be a showing the user they have a new message from another user. With Pusher, you can push a message to the receiver's browser, so they get an immediate notification if they are logged in. For a really great demo of what Pusher can do, check out http://wordsquared.com/
You can send any data you like, such as a JSON hash to interpret how you like it, but it would be very convenient to send RJS, just like with any other ajax call and eval() it on the client side. That way, you could (for example) render the template for your menu bar, updating it in its entirety, or just the new message count displayed to the user, using all the same partials to keep it bone-DRY. In principle, you could render the partial from the sender's controller, but that doesn't make much sense either, and there might not even be a request, it could be triggered by a cron job, for example, or some other event, like a stock price change. The sender controller just should not have to know about it - I like to keep my controllers on a starvation diet ;)
It might sound like a violation of MVC, but it's really not - and it really should be solved with something like ActionMailer, but sharing helpers and partials with the rest of the app. I know in my app, I'd like to send a Pusher event at the same time as (or instead of) an ActionMailer call. I want to render an arbitrary partial for user B based on an event from user A.
These links may point the way towards a solution:
http://blog.choonkeat.com/weblog/2006/08/rails-calling-r.html
How to render a Partial from a Model in Rails 2.3.5
http://mattwindsurfs.wordpress.com/2008/06/19/rails-render-in-a-model/
http://davetroy.blogspot.com/2008/02/actsasrenderer-brings-output-to-models.html
https://github.com/asapnet/acts_as_renderer
http://ethilien.net/archives/render-rails-templates-anywhere-even-in-a-model/
The last one looks the most promising, offering up this tantalizing snippet:
def render_anywhere(partial, assigns)
view = ActionView::Base.new(Rails::Configuration.new.view_path, assigns)
ActionView::Base.helper_modules.each { |helper| view.extend helper }
view.extend ApplicationHelper
view.render(:partial => partial)
end
As does this link provided by another poster above.
I'll report back if I get something working
tl;dr: me too!
I just do this:
ApplicationController.new.render_to_string(partial: 'messages/any', locals: { variable: 'value' })
Rails 5 way
In Rails 5 rendering outside a controller became pretty straightforward due to implemented render controller class method:
# render template
ApplicationController.render 'templates/name'
# render action
FooController.render :index
# render file
ApplicationController.render file: 'path'
# render inline
ApplicationController.render inline: 'erb content'
When calling render outside of a controller, one can assign instance variables via assigns option and use any other options available from within a controller:
ApplicationController.render(
assigns: { article: Article.take },
template: 'articles/show',
layout: false
)
Request environment can be tailored either through default options
ApplicationController.render inline: '<%= users_url %>'
# => 'http://default_host.com/users'
ApplicationController.renderer.defaults[:http_host] = 'custom_host.org'
# => "custom_host.org"
ApplicationController.render inline: '<%= users_url %>'
# => 'http://custom_host.org/users'
or explicitly by initializing a new renderer
renderer = ApplicationController.renderer.new(
http_host: 'custom_host.org',
https: true
)
renderer.render inline: '<%= users_url %>'
# => 'https://custom_host.org/users'
Hope that helps.
You can use ActionView directly and render partials to string without having a controller. I find that pattern useful to create models that encapsulate some javascript generation, for instance.
html = ActionView::Base.new(Rails.configuration.paths['app/views']).render(
partial: 'test',
formats: [:html],
handlers: [:erb],
locals: { variable: 'value' }
)
Then, just put your _test.html.erb in you view folder and try it out!
Rails 6.0.0 compatible answer, since I ended up on this page while searching for a solution:
lookup_context = ActionView::LookupContext.new(Rails.configuration.paths["app/views"])
renderer = ActionView::Base.new(lookup_context)
renderer.extend(Rails.application.helpers)
renderer.render \
template: "foo/bar",
formats: [:html],
handlers: [:erb],
locals: { user: User.new }
I'm fairly sure the answers you seek lie within Crafting Rails Applications where Jose Valim goes into great detail about how and why you would want to render views straight from your db
Sorry I can't be of more help yet because I've just started reading it myself tonight.
You might find some help here - it's a blog post about doing this sort of thing, albeit using different methods than yours
the "proper" way to do this is to push an object in serialized form(json), and then have the view deal with it once the event is received. Perhaps you want to use Handlebars to render the object.
Edit: I originally wrote about how, despite my answer, I was going to follow your example. But I just realized there is a HUGE gotcha with your approach when it comes to push notifications.
In your problem, you are doing push notifications to one user. For me, I was broadcasting out to a set of users. So I was going to render html with a presumption of a "current_user" and all that comes with it(eg logic, permissions, etc). This is NO BUENO as each push notification will be received by a different "current user".
Therefore, really, you need to just send back the data, and let each individual view handle it.
You should call all render methods from a controller. So, in this case, you can notify the controller that the object has been created and the controller can then render the view. Also, since you can render only once, I think you can wait for all your server side operations to complete before invoking the render.
The render methods are defined on the ActiveController class and its progeny. Inherently you do not have access to it on the model, nor is it a class method so you can't use it without an instance of the controller.
I've never tried to instantiate a controller for the express purpose of simply stringifying a partial, but if you can get your hands on a controller, render_to_string seems to be the way to go.
I will chime in by saying that if you're going down this path you're taking RoR "off the Rails". This is a violation of MVC and fundamentally poor program design.This doesn't mean I think you're a bad person :P Sometimes life drives us off the rails, so to speak.
I can't speak to the details that have driven you to do this, but I'd strongly suggest you rethink your approach.
I have created a gist for this.
I needed something similar, where the models don't necessarily (or in my case, ever) get updated via a controller, so the logic can't sit there.
Created a server-push based controller:
https://gist.github.com/4707055

Proper Caching Strategy for a Dynamic Action

Probably an obvious question for those of you who have scaled/cached anything before. I haven't, and I'm getting lost in the tutorials and code snippets all over the internet (http://guides.rubyonrails.org/caching_with_rails.html).
I'm deploying to Heroku with Memcached installed and am figuring out the most optimized way to do the following:
Query the database to find a post and see if it has been 'flagged'
Query a Whitelist to see if a different part of the post has been 'flagged'
Query an API to see if they find this user in their system
Render a page with a lot of repetitive calls to remote systems for CSS/JS/etc.
I assume #1 happens frequently and changes often. #2 less so. #3 changes infrequently (months), and #4 should only change if #3 changes.
I want to be able to increment flag_count and view_count regularly without hitting a cached version. What mix of page, action and fragment caching should I be doing? Right now, I'm not caching this action at all...
My [simplified] controller code:
def show
expires_in 12.hours, :public => true
#post = Post.find(params[:id])
#CHECK FLAG STATUS
redirect_to root_path and return if #post.flag?
#CHECK WHITELIST STATUS
redirect_to root_path and return if Whitelist.includes?(#post.screen_name)
#Ping API again on the off chance user deleted/changed account
if #post && #post.user = get_user_from_api( #post.screen_name )
#post.increment_views!
render :layout => false
else
redirect_to root_path
end
end
There's a few small things that might work with this. Tricky given there's no way to avoid hitting the app stack each request.
Fragment Caching
Use fragment caching with memcache to avoid regenerating post/comment content. There might be some gains here if your views are heavy. Memcache objects self-expire and can be keyed to the most recent version of a post with something like:
<% cache #post.cache_key do %>
<%= #post.formatted_content %>
...
<% end %>
Other things to consider
Setup server to send cache headers with images, JS & CSS
Check out barista to reduce requests & bundle assets

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