How do I cache dynamic header/footer in a rails app? - ruby-on-rails

Say I have a simple app with the following layout
<html>
<head>...</head>
<body>
<%= render :partial => 'layouts/header' %>
<%= yield %>
<%= render :partial => 'layouts/footer' %>
</body>
</html>
My header and footer partials are pretty simple but they require dynamic data which takes a a few ms to fetch from the db. Right now, I get the header data with a before_action: method in my application controller since it's need on a all pages (except Ajax calls):
before_action :prepare_header
private
def prepare_header
#news = ...
end
How can I cache either the prepare_header action or header.erb.html.
I am using Rails 4.0.0
Thank you.

You can do it with fragment caching in the view.
Your #news variable is calculated based on some data, for example params[:a], params[:b] and params[:c]. Based on that you can wrap your header (and then partial) with a cache block
<% cache([params[:a], params[:b], params[:c], 'header']) do %>
you html code here
<% end %>
with that you can have a cached version of your partial depending on the desired control variables. Check here for more on view caching.
A good practice is to add more control to the cache_key in order to invalidate it on demand, like:
cache([cache_key_1, cache_key_2, version_number])
when something changes you bump the version_number and invalidate accordingly!
And of course an expires_at parameter.
UPDATE
For action caching you can try this gem. And pass your desired params in an
if: Proc { ... }
parameter

Related

Is it ok to call ActiveRecord Methods in the view Rails

Using Rails 4
I am wondering (and having a hard time finding an answer) if it is OK to call an ActiveRecord method directly from the view, such as:
<%= Article.where(approved: true).count %>
or
<%= Article.where("short_answer is NOT NULL and short_answer != ''").count %>
I realize the normal practice would be to store these in an instance variable inside of the controller, but since I am using a partial, I cannot do that.
Is doing this ok? Can it hurt? Is there a better way to go about this (such as a helper method)? Thank you!
Is doing this ok? Can it hurt?
It's definitely okay, but the problem is that you'll be calling another db query - which is the most "expensive" part of a Rails app.
#instance_variables are set once, and can be used throughout the view:
#app/views/articles/show.html.erb
#Referencing #article references stored data, not a new DB query
<%= #article.title %>
<%= #article.description %>
<%= #article.created_at %>
Because the above all uses the stored #article data, the database is only hit once (when #article is created in the controller).
If you call AR methods in the view, you're basically invoking a new db call every time:
#app/views/articles/show.html.erb
#Bad practice
<%= Article.select(:name).find(params[:id]) %>
<%= Article.select(:description).find(params[:id]) %>
<%= Article.select(:created_at).find(params[:id]) %>
To answer your question directly, you would be okay to call that data IF you were only counting database-specific data.
IE if you were trying to count the number of #articles, you'd be able to call #articles.size (ActiveRecord: size vs count)
The prudent developer will determine which data they have in their controller, and which they need to pull from the db... doing all their db work in the controller itself:
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.where(approved: true) #-> could use a scope here if you wanted
end
end
#app/views/articles/index.html.erb
<%= #articles.size %>
Nithin's answer is great but won't get past the consideration that you have to determine whether you need to call the db explicitly, or use already-invoked data.
Finally, in regards to using a partial, if you have to pass that data every time, you may wish to use some sort of conditional data to determine whether you need to call the db or not:
#app/views/shared/_partial.html.erb
<% approved ||= Article.approved_articles.size %>
<% short ||= Article.short_answer_presence.size %>
This will allow you to set locals IF you want, and also have "defaults" set if they aren't set.
You should mostly do
class Article < ActiveRecord::Base
....
scope :approved_articles, where(approved: true)
scope :short_answer_presence, where("short_answer is NOT NULL and short_answer != ''")
end
In your controller method
#approved_articles_count = Article.approved_articles.count
#short_answer_presence_count = Article.short_answer_presence.count
and use those variables in view.
In case of partials, as said my Raman you can do that.
<%= render partial: "form", locals: {approved_articles_count: #approved_articles_count, short_answer_presence_count: #short_answer_presence_count} %>
You can always pass these variables inside a partial using locals:
<%= render partial: "form", locals: {zone: #zone} %>
Its always a good practice to define the instance variables in controller, it does not hurt but you don't end up doing business logic inside a view.

Adding extra layout to specific controllers and actions in rails 3?

In my application.html.erb I have a header partial. which I rendered with the render tag
<%= render 'layouts/header' %>
So this header applies to all the controller and all the actions.
I have a dropdown partial which i want to show, in addition to the header partial, in all the controllers except one one controller. I want something like
<%= render 'layouts/dropdown' except_controller_anycontroller %>
When I put
render :partial => 'layouts/dropdown'
It just renders the dropdown partial and all other layouts are lost (like the footer,header,body). I want to add the extra dropdown partial only to certain actions and controllers.
How can I achieve that in Rails 3.2.13?
Replace your render with this:
<%= render 'layouts/dropdown' unless #disable_dropdown %>
Then you can simply set disable_dropdown to true in any controller you like:
def test_method
#disable_dropdown = true
end
call this method in your controller filter, in which you dont want to show this:
write this on top of your controller above your first method:
before_filter :test_method
it will automatically be called when your request comes to this controller.
Hope it will help. Thanks
I would suggest something like:
<%= render 'layouts/dropdown' unless params[:controller] == "controller_to_avoid" %>

RoR side content to appear on multiple pages

If you have a side table on your page with info from the database, and you want it to appear on several pages in your RoR app. How should this be done?
Let's say you output this data in a view, called LeftTableView or something like that.
Now in the, "/products/" view you want to display this data, and you also want to display it on "/products/12" and also on "/friends/" and so on.
How do you render out this "partial" view and bind the database data using the original controller only? Or... is it "better" to collect the data again from each controller?
Assumed, if you want appear "category" on navigation from database, keep in application_controller.rb
def navcategory
#navcategories = Category.all
end
on products_controller.rb and friends_controller.rb , you just call navcategory with before_filter
before_filter :navcategory
and on layout
<% #navcategories.each do |category| %>
....
....
....
<% end %>
you should setup your layout. try the following (no css)
# application layout
<body>
<div id='sidebar'>
... common content here
</div>
<div id='main'>
<%= yield %>
</div>
</body>

Dynamic Sidebar with Rails layout

For instance, i want to have my sidebar to have several dynamic content. Using other method will lead me to put query codes into View, which is not a good idea at all. I would like to keep any query in my Controller.
Currently as i know there are several ff. method:
Render a shared partial -> No where to put the query
render :partial => "shared/sidebar"
Content For -> Additional details in the comment
<%= yield :sidebar %>
<% content_for :sidebar do %>
Netscape<br>
Lycos<br>
Wal Mart<br>
<% end %>
3rd is write it directly to the layout file.
So how should I make this work?
IF you want this in every view, you can place the method that populates the necessary data in application_controller and use a before_filter to trigger it.
before_filter :load_sidebar
def load_sidebar
#data = Thingy.find(:all)
end
Then your partial or content_for element checks for #data and processes.
If you wanted to reduce the amount of code in your application_controller.rb, you may want to consider using the Cells gem.
This would allow you to define your 'query' in a separate cell controller, and you would render the content for it using something like render_cell :sidebar, :myquery inside your view.

Instance variables in layout

I am fairly new to rails so I apologize if I am using the wrong terminology.
I have a model Menuitem that I would like to display the contents of in a layout. How does one go about passing an instance variable into a layout?
I was looking for a layout helper of some sort but I was unable to find anything. I was also looking at defining the instance variable in the application controller to access it in the layout, would this work? If so what is the best way to go about doing it?
Thanks!
The usual way of passing variables up from the view into the parent layout is to use the content_for method. (This answer is a copy + paste from a similar answer I posted at this question)
The normal view content gets rendered automatically into the yield call without an argument in the layout. But you can also put other placeholder content in by using yield with a symbol argument, and specifying that content from the view with content_for.
app/views/layouts/posts_layout.html.erb
<html>
<head>
<title>My awesome site</title>
</head>
<body>
<div id="someMenuStructureHere">
<%= yield(:menu_items) %> <!-- display content passed from view for menu_items -->
</div>
<%= yield %> <!-- display main view content -->
</body>
</html>
app/views/posts/index.html.erb
<%= content_for :menu_items, some_helper_to_generate_menu %>
<h1>Here is you page content</h1>
Two things I would note. First, you probably don't want to be doing this query every time you render any page in your application. You definitely want to cache your MenuItems. Second, it might be helpful to put a convenience method on MenuItems class to cache this value. So, if I define a method
def MenuItem.all_for_menu
##all_for_menu ||= MenuItem.find(:all) #returns value if exists, or initializes it
end
I can call MenuItem.all_for_menu in my layout and get all the menu items. When ever you add a new one or edit one, you'd have to invalidate that.
Another caching approach would be to put the data in a partial and cache that fragment using the standard caching call:
<% cache(:controller => "menu_items",
:action => "list",
:action_suffix => "all_menu_items") do %>
<%= render :partial => "menu", :collection => MenuItem.all_for_menu %>
<% end %>
You can then expire that fragment by calling:
expire_fragment(:controller => "menu_items", :action => "list", :action_suffix => "all_menu_items")
Any instance variables defined in the controllers are auto-magically available in your views. If you are expecting an instance variable in your layout for all actions, you may want to consider defining the instance variable in a before_filter or encapsulating it in a controller method and using helper_method to make it accessible in your views.
It really depends on what you want to do with the model. I'll just guess, and you tell me what you need different to understand better how to do this. This code would work only if your MenuItem model has a field named name.
In the controller:
# Use whatever action you are currently displaying
def index
#menu_items = MenuItem.all
end
In the index.html.erb view file:
<ul id="menu">
<% #menu_items.each do |menu_item| %>
<%= h menu_item.name %>
<% end %>
</ul>
Obviously if this was a real menu, there would be hyperlinks there too :)
items_controller.rb (or something)
def show
#menu_item = MenuItem.find(params[:id])
end
In the view show.html.erb:
<%= #menu_item.name %>

Resources