1. Computing

Step 4: Working on the Views

By

Cleaning up From Scaffolding
Step 4: Working on the Views

OK, the blog is coming along nicely. We've set up authentication using Devise and now have a technically functional blog, but the views are still just the default views output by the scaffold generator. It's time to fix that and make this into an actual blog. While we're at it, we'll work on pagination so only a few blog posts display on any given page and integrate RedCloth, which reads simple textile markup and outputs HTML.

The finished code for this tutorial can be found on GitHub and the complete code (in whatever state that might be) in the main repository. In order to clone this specific version of the project, first clone the project, then switch to the tag called v0.4.


$ git clone https://github.com/mmorin/blogtutorial.git
…
$ cd blogtutorial
$ git checkout v0.4

A Post Partial

Right now, posts are rendered in two places. One in app/views/posts/index.html.erb and another in app/views/posts/show.html.erb. This is not very DRY (Don't Repeat Yourself), and we should change that. A typical blog will display posts the same whether you're listing posts on the front page, or have clicked on a link that takes you to a specific post. Occasionally, a blog will only render the first few paragraphs of a blog post if rendered on the front page, but even then, we can DRY up our posts. We'll start by creating the file app/views/posts/_post.html.erb.

A partial is a bit like a method for views. It abstracts some smaller portion of the problem into an isolated unit and you will call on that unit from a number of places. And apart from a normal view, there's very little different about a partial. The big difference we'll see is that instead of accessing our @post variable as in the views output from scaffold, we'll be accessing the post local variable in our partial. Views generally access instance variables shared with their controller, but partials generally access local variables passed to them via the render method. By default, there's a local variable with the same name as the partial, in our case post. We'll see how that comes into play when we actually render the partial.

The structure of the HTML output of your Post partial is up to you. In my case, I kind of have create output that agrees with the WordPress theme and its CSS style. So while your HTML structure might look different here, the code inside the ERb tags should look similar. Here is app/views/posts/_post.html.erb.


<div id="post-<%= post.id %>">
<h1 class="postTitle"><%= link_to post.title, post %></h1>
<p class="date"><small><%= post.created_at.to_date.readable_inspect %> by No One</small></p>
<div class="post">
<%= simple_format post.body %>
</div>
<%- if user_signed_in? && current_user.is_blogger %>
<small>
<%= link_to 'Edit', edit_post_path(post) %> |
<%= link_to 'Destroy', post, :method => :delete %>
</small>
<%- end %>
</div>

There aren't many surprises here. For now, simple_format is used on the post body to create some simple paragraph tags, we'll implement RedCloth in a moment. The 'Edit' and 'Destroy' links are only shown if the user is logged in and is a blogger. And the rather cumbersome post.created_at.to_date.readable_inspect creates a human readable string such as Tue, 12 Feb 2013 from the date portion of the created_at datetime. To render this partial, we delete everything in app/views/posts/show.html.erb with just this one line.


<%= render 'post', :post => @post %>

The object we pass to the partial gets assigned to the post local variable inside the partial. And then, from app/views/posts/index.html.erb, we have this single line.


<%= render :partial => 'post', :collection => @posts %>

This is doing the same thing, except we have to be a bit more explicit. The :collection key must be passed in conjunction with an explicit :partial key, or else Rails cannot differentiate between you wanting a local variable called "collection" or a collection of partial renders. The collection shortcut is the same as iterating over @posts and rendering the partial for each object.

And now, since we've deleted the links to the post#new action, we'll put that into the app/views/common/_session.html.erb partial.


<%- if user_signed_in? %>
<small>
<%= current_user.email %><br />
<%= link_to 'Sign out', destroy_user_session_path, :method => :delete %>
<%- if current_user.is_blogger %>
| <%= link_to 'New post', new_post_path %>
<%- end %>
</small>
<%- else %>
<small>
<%= link_to 'Register', new_user_registration_path %> |
<%= link_to 'Sign in', new_user_session_path %>
</small>
<%- end %>

And that does it for the views that users can see. The post#new and post#edit views are never seen by normal users, and they're functional as they are. It's up to you whether you want to work on them much. I did spruce mine up a bit though so they fit with the style a little better.

Here is app/views/posts/new.html.erb.


<div id="post-1">
<h1 class="postTitle"><a href="#">New Post</a></h1>
<div class="post"><%= render 'form' %></div>
</div>

And app/views/posts/edit.html.erb.


<div id="post-1">
<h1 class="postTitle"><a href="#">Edit Post</a></h1>
<div class="post"><%= render 'form' %></div>
</div>

But that's not very DRY. We already have a partial we're using, the only thing different here is the title. All we have to do is move the extra markup into the app/views/posts/_form.html.erb partial like so:


<div id="post-1">
<h1 class="postTitle"><a href="#"><%= page_title %></a></h1>
<div class="post">

<%= form_for(@post) do |f| %>
<% if @post.errors.any? %>
<!-- Snipped for brevity -->

We now have a page_title local that we need to pass along every time we call this partial. The app/views/posts/new.html.erb now reads:


<%= render 'form', :page_title => 'New Post' %>

And app/views/posts/edit.html.erb reads:


<%= render 'form', :page_title => 'Edit Post' %>

Adding a Helper

There's something that bothered me with the code from the previous section. When we display a date, we use post.created_at.to_date.readable_inspect. To anyone reading this code in the future, it doesn't really tell them what we're really doing. So let's abstract this out into a helper. Since this is a generic helper, it's likely to be used by other controllers since it has to do with Date objects in general, we'll put this in app/helpers/application_helper.rb.


module ApplicationHelper
  def readable_date(d)
    d = d.to_date unless d.is_a?(Date)
    d.readable_inspect
  end
end

And now in app/views/posts/_post.html.erb we can simply say this:


  <p class="date"><small><%= readable_date(post.created_at) %> by No One</small></p>

Pagination

Fire up the blog and make about 20 test posts. Or, if you're clever, do this using the Rails console.


$ rails c
Loading development environment (Rails 3.2.11)
2.0.0-rc2 :001 > 20.times{ Post.new(title:'test',body:'test').save }
   (0.0ms)  begin transaction
  SQL (11.9ms)  INSERT INTO "posts" ("body", "created_at", "title", "updated_at") VALUES (?, ?, ?, ?)  [["body", "test"], ["created_at", Wed, 13 Feb 2013 19:24:32 UTC +00:00], ["title", "test"], ["updated_at", Wed, 13 Feb 2013 19:24:32 UTC +00:00]]
…

Now go back to your blog's root URL. What do you notice? First, the oldest post is being shown first. Second, we have 20+ blog posts being displayed on a single page. If your blog posts are long, this is a bit much. We need to limit this to 5 blog posts. This used to be something built into Rails, but has since been removed as it turns out that what appeared to be a simple problem, everyone had their own ideas how to solve it. We will be using the will_paginate gem.

This is a very easy solution, all we need to do is add the will_paginate gem to our Gemfile and run bundle install. And as always, after running bundle install you have to restart the Rails server. The relevant part of our Gemfile now looks like this.


# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'sqlite3'
gem 'devise'
gem 'will_paginate'

Next, go on over to app/controllers/posts_controller.rb and find the index action. This is the only place we'll want to paginate anything. The only line we're really concerned with is the very first line of the method. Right now it's just fetching all posts, we only want it to fetch 5 posts at a time. And while we're at it, we're going to define the order they're displayed on the index page.


  # GET /posts
# GET /posts.json
def index
@posts = Post.paginate(:page => params[:page], :per_page => 5).order('created_at DESC')

However, I find this a bit difficult to read. I prefer this less-than-idiomatic but easier to read syntax.


  # GET /posts
# GET /posts.json
def index
@posts = Post
.paginate(:page => params[:page], :per_page => 5)
.order('created_at DESC')

If you load up the index page, you'll see that the newest 5 posts are indeed being displayed now, but there are no links to the other pages. We can fix that, open up the app/views/posts/index.html.erb view. We just need to add one line to the bottom.


<%= render :partial => 'post', :collection => @posts %>
<hr />
<%= will_paginate @posts %>

Great, this looks like it works just fine. But you'll notice that it's generating URLs like /posts/?page=3. This is not pretty or Rails-like. We can fix this easily by adding a new route to config/routes.rb.


Blogtutorial::Application.routes.draw do
devise_for :users

match 'posts/page/:page' => 'posts#index'
resources :posts

root :to => 'posts#index'

Now will_paginate will generate links like /posts/page/3 instead. A minor improvement, but it's nice. And just remember that this new match line must go before the resources line for the Post model. Routes are first come first serve, the first route in the file that matches the pattern gets chosen.

And there are of course other pagination gems out there, but for a use this simple, this gem is just fine.

RedCloth Formatting

And this final touch is just a simple as the pagination gem. The RedCloth gem implements the Textile markup language, allowing us to format our blog entries instead of just leaving them as unformatted text. Start by adding the RedCloth gem to your Gemfile, running bundle install and restarting the Rails server.


# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'sqlite3'
gem 'devise'
gem 'will_paginate'
gem 'RedCloth'

Now, instead of using simple_format post.body in app/views/posts/_post.html.erb, you use raw textilize(post.body). And that's it, you can now format your posts using Textile. Here's a reference I'm sure you'll find useful. But wait, what was that raw method? It used to be you'd need to manually escape HTML characters in everything you output in a Rails view. But escaping is now the default behavior and since the textilize helper method exists to output HTML markup, we want to tell Rails not to escape those characters. This is a security feature, and makes it quite difficult to slip up and let a XSS (Cross-Site Scripting) vulnerability sneak in since you need to tell rails explicitly which HTML you want to let through.

  1. About.com
  2. Computing
  3. Ruby
  4. Ruby on Rails
  5. Ruby on Rails 3 Tutorials
  6. A Full Featured Blog
  7. Step 4: Working on the Views

©2014 About.com. All rights reserved.