Reusing code in Rails templates
How to use partials applying DRY(Don’t Repeat Yourself) on Rails views
Rails partials
views (those that get rendered in the context of other views) allow us to reuse code. They have several differences to action's views
rendered in a controller action.
For example if we have a controller like this:
class SamplesController < ApplicationController
def show
render 'show' #this can be omitted
end
end
We should have a a view
file inside app/views/samples
called show.html.haml
with the following content:
= content_for :header do
= render 'extra_header'
Hello World! Server time: #{Date.now.strftime('%m/%d/%Y')}.
= content_for :footer do
= render 'extra_footer'Note: we are using HAML as markup template language for the view's code examples on this post
On this file we can also see calls to render
method as in the controller. However this invocations are from different nature: instead of rendering an action's view
it allow us to render a partial
inside a particular view. Let’s see the main differences between this two types of views.
Where are partial views?
By default, if no view path is specified, Rails assume app/views/<resource>
. For this particular example an action's view
is located by default on app/views/samples
due to that the controller is called SamplesController
.
Partial views like in this example: extra_footer
and extra_header
, will be also located on the same directory: app/views/samples
. The difference is that partial views are prefixed with an underscore so we will have _extra_footer.html.haml
and _extra_header.html.haml
.
Difference in redering
It’s very important to emphasize that the render
method from a controller call it’s not the same that render
method called from a view. The views that are rendered from a controller doesn’t need to be prefixed with underscore unlike partials
.
Additionally passing local variables is different in each case. For example if we wish to pass a variable foo
to an action's view
we need to write something like:
class SampleController < ApplicationController
def show
render 'show', locals: {foo: 123.45}
end
end
And on the show.html.haml
action view we should modify the content to access the passed local variable:
= content_for :header do
= render 'extra_header'
Hello World!. Server time: #{Date.now.strftime('%m/%d/%Y')}.
The content of foo variable is: #{foo}
= content_for :footer do
= render 'extra_footer'
If we want to pass some parameters to a partial
view instead the syntax is slightly different. Assuming that we have the following on _extra_header.hmlt.haml
partial
to display the content of a local variable called bar
:
Hello World! Bar's value is: #{bar}
we must adjust the render call like this:
= content_for :header do
= render 'extra_heading', bar: 678.9
Hello World!. Server time: #{Date.now.strftime('%m/%d/%Y')}.
The content of foo variable is: #{foo}
= content_for :footer do
= render 'extra_footer'
Here the difference is that we don’t need to pass all the variables inside a locals
parameter.
Layouts and content_for
Action’s views
When an action's view
gets rendered is going to be wrapped inside a layout. A layout is similar to a template that includes most of assets like: .js
, .css
, .js.coffee
, .css.scss
and define a base skeleton of our app. These are found in app/views/layouts
using application.html.haml
by default.
Inside the layout we are going to have html standard content with haml
, ruby
, javascript
… embeded:
!!!
%html
%head
%title Hello!
/ rest of header content and include sentences for .js, .css
/ or other assets
%body
= yield :extra_heading
= yield
= yield :extra_footer
Here we have three yield's
sections, but only two of them are named. Although both types of yields
give the rendering control to a view
the are very different:
- Their should be only one nameless
yield
that will give the rendering control of theview
that we command to from the controller. - We should have for every
:name
that we want to use ayield :name
instruction. These are going to give the rendering execution to everycontent_for :name
that the rendering process find.
Base on this we can stablish that:
= yield
will render the correspondingaction's view
fo the controller, in this caseapp/views/samples/show
= yield :extra_header
and= yield :extra_footer
are put in a stack to get processed at the end.- When the application starts to render the
show
view it will find twocontent_for
instructions that will render the correspondingyields
instructions.
Finally the partial view
_extra_header.html.haml
get’s rendered inside the instruction = yield :extra_header
and the same with the footer.
We will always have a layout action view
for our controller methods like render show
. Rails will use the application layout app/views/layouts/application.html.haml
. To change that behaviour we can specify a new layout to the controller:
class SamplesController < ApplicationConroller
layout 'another_layout'
def show
render 'show', locals: {foo:123.45}
end
end
In this case we should have another layout at app/views/layouts/another_layout.haml
that will get rendered for every controller action. If we want to exclude some action from using this new layout and use the default application layout we can use only
or excep
instructions:
class SamplesController < ApplicationController
layout 'another_layout', except: [:do_something_else,
:do_something_else2]
def show
# the used layout will be another_layout.html.haml
render 'show', locals: {foo:123.45}
end
def edit
# it will render the another.layout.html.haml layout
end
def do_something_else
# it will render application.html.haml layout
end
def do_something_else2
# it will render application.html.haml layout
end
end
Additionally we can specify which layout will be used on the render
method. This will have more precedence than the layout specified on the controller.
class SamplesController < ApplicationController
layout 'another_layout', except:[:do_something_else,
:do_something_else2, do_something_else3]
# .
# ..
# ...
def do_something_else3
render layout: 'third_layout'
# it will render third_layout.html.haml
end
end
Partials views
We could say that the rendering of a partial view
doesn’t have the ability to wrap inside a layout, like action's view
do. Invoking something like:
= render 'mi_partial', layout: 'some_layout'
doesn’t have the same meaning. Why? Because a partial view
doesn’t need to get wrapped on some application skeleton.
The first reason is that a partial layout
it’s not for that but for showing a piece of data that eventually we can reuse.
Secondly a partial view
has the ability to be a layout itself.
Let’s see an example creating a third partial view
, that will be used for the header and footer. Inside app/views/samples
we should create a file called _extra_section.html.haml
with the following content:
%section{class: extra_class}
= yield
This partial is very basic but accepts two premises:
- It could receive local arguments, as seen before.
- Accepts content with a
= yield
instruction likewise anaction's view
.
The big difference here is when we are going to use the view. For this example in our _extra_header
view we would have something like:
# Rails 4
= render layout: 'extra_section', extra_class: 'mi-heading' do
Hello world! Bar's value is #{bar}
# Rails 5 this syntax if used on Rails 4 will not render the content
= render 'extra_section', extra_class: 'mi-heading' do
Hello world! Bar's value is #{bar}
Something analogous can be coded for the footer.
Summing up
There are two ways of reusing code on Rails views so we don’t repeat ourselves (DRY principle).
Action’s views/General layouts: allow us to define the general structure of our site pages. by having a =yield
sentence we can render automatically the desired content.
Partial Views: allow us to define a reusable content that can serve as structure in a render
sentence(inside a view) as wrapping structure of another content that contains a yield
or a yield value
sentence we we going to insert the desired content.
Author: Eng. Luis Masuelli