Super-simple blog app with Ajax and Restful Rails (Rails 2.0)

November 16, 2007

Thanks to rubyphunk from railsforum.com for giving me a lot of help with this. For anyone reading, i’m assuming you’ve got a basic understanding of rails already.

I’m learning ajax and restful rails at the moment, which is a hard thing to do because they both involve routing, and both have great power to confuse (me at least). So, here’s how i do a table of records which uses ajax to refresh the table when a record is deleted. I also show a way (possibly not the best way) to do a table with alternately coloured columns.

This is written in rails 1.2.5, aka ‘Rails 2.0′, but if you’re using an earlier version it should still work, provided you use the right scaffold (i’ll explain later).

Anyway, let’s get on with it – to learn about ajax and restful rails we’re going to make an app from scratch. The app is going to be super-simple – it’s a blog app which has one model, ‘Entry’ (don’t ever call a model ‘Post’ when doing restful rails. You’ll hurt your brain. Trust me) , which has a title and body field, along with the usual created and and added at timestamps. The index page will show a table of blogs, with just the start of the title and body shown, along with timestamps, and an edit and delete option – all the usual simple scaffoldy stuff.

The ajax will be used only with the delete command – when someone deletes a post, we want the table to update withoout reloading the page. To check that the page hasn’t refreshed, we’re going to put the current time on the page, which will only be updated when the page has reloaded. So, if we see the table update without the time changing, we’ve succeeded. Oh, and we’re also going to give the table vertical stripes, for no other reason than i was curious about how to do that. Let’s go.

in your rails app directory:

rails ajaxblog
cd ajaxblog

now, let’s go in and make the scaffold for our single model, ‘Entry’. This has a ‘title’ which is a string, and a ‘body’ which is text (long string). Don’t worry about the timestamp fields for now.

ruby script/generate scaffold_resource entry title:string body:text

(if you’re using rails 1.2.5, ‘scaffold_resource’ won’t be recognised. Just use “scaffold” instead. scaffold_resource is the old restful scaffold, which has now become the standard in 1.2.5 onwards. I specified that here because it’s better to put in the wrong one and get a complaint, than to put in the wrong one and just get the wrong kind of scaffold made)

Now, rails has kindly generated a migration for us, so lets make a database for it to use:

mysqladmin -u root create ajaxblog_development

This is the default name specified in config/database.yml, so that’s all we need to do.

Now, go and look at the migration in db/migrate – 001_create_entries.rb:

class CreateEntries < ActiveRecord::Migration
def self.up
create_table :entries do |t|
t.string :title
t.text :body

t.timestamps
end
end

def self.down
drop_table :entries
end
end

We can see that because we told the scaffold about ‘title’ and ‘body’, it’s set up the migration for us with those fields specified. It also provides requests timestamps (updated_at and created_at) by default. So, we don’t need to do anything with the migration. I just wanted to show you something nice :)

So, close the migration without making any changes, and run

rake db:migrate

Cool. So, we should have a restful blog app now. Start a server (“mongrel_rails start” or your equivalent), go to the address specified (eg http://localhost:3000) and you should see the rails start page. Add “/entries” to the end of the url, and you should see a page with a table for entries.

So, have a play around with that. Notice that the urls are different from traditional rails – for example, the page with the list of entries would normally have been associated with an action called ‘list’ (or similar) in EntryController, and therefore have a url of /entry/list. Now it’s just /entries, which automatically calls the action “index”, which is used to provide a list of entries. This is how rest works – the urls are kept as simple and as standardised as possible, and we tell actions apart based (with some exceptions) on the type of http request rather than the method names in the controller.

Use the interface to add an entry, and you’ll be taken to the ‘show’ page for the new entry. Before, the url would have been something like /entry/show/1. Now it’s simply /entries/1. So, again, we’ve missed out the action name, but the app knows what to do because when we requested the show action, we sent, via the browser, a GET request, and GET requests, when combined with a url of /entries/1, are associated with the show action. I’m not going to go into REST in too much detail here, there’s plenty of other stuff out there.

To illustrate this, let’s make our first change to the scaffold code. When someone adds their entry, we want to go straight back to the index page, to see it on the table. So, we need to edit the create action, which adds the new entry (using info from the form on the ‘new’ page) to the database.

In entries_controller.rb, in the create action, we have

# POST /entries
# POST /entries.xml
def create
@entry = Entry.new(params[:entry])

respond_to do |format|
if @entry.save
flash[:notice] = ‘Entry was successfully created.’
format.html { redirect_to(@entry) }
format.xml { render :xml => @entry, :status => :created, :location => @entry }
else
format.html { render :action => “new” }
format.xml { render :xml => @entry.errors, :status => :unprocessable_entity }
end
end
end

The respond_to block might look a bit alien – don’t worry about it for now. We’re only going to edit this line -
format.html { redirect_to(@entry) }

This is called when the record is successfully added and html is requested, which is the default file type – ie, we’re only worrying about which html page we get sent to next. Change this line to be
format.html { redirect_to entries_path }

entries_path is a helper which basically remembers the url “/entries” for us. This should take us to index after we submit a new post.

Next task – make a nicer table. In particular, lets have one that shows the created_at and updated_at data, has fixed width columns, and also has alternately coloured columns, so we can tell them apart easier.

The table is going to have two sections – the header row, where we choose names for each column, and also specify the widths, and then the record rows, which we’ll build with a for loop in the usual rails way (“for entry in @entries”, etc) . Now, the code to populate each column is going to be quite varied, because we’re not going to simply displaying the raw data. Text needs to be truncated to fit into the table, and we want our time stamps to have the format “about 1 hour ago, 2 days ago, etc”. So, we’re going to put all of this ruby code into a helper, which we call in the view. Clean views are good!

In addition, to keep things even cleaner (and to help with the ajax later) we’re going to put the table into a partial, passing through @entries as a local variable (entries, without the @) for the partial to use to populate the table.

So, edit the app/views/entries/index.rhtml (might be html.erb if you’re using the latest rails) view to look like this:

<h1>Listing entries</h1>

<%= link_to ‘New entry’, new_entry_path %>
<hr/>

<div id=”entry_table”>
<%= render :partial => “entries”, :locals => {:entries => @entries} %>
</div>
<hr/>
<%= link_to ‘New entry’, new_entry_path %>

This is all pretty standard stuff, except for the “New entry” link. Whereas previously we’d have specified an action, eg :action => “new”, we now just pass along the path helper, in this case “new_entry_path”. This is just another way to standardise things, and increase our DRYness. We don’t have to worry about the new method being renamed to “add” or something – in fact we don’t even see what the method is called, though it should be called ‘new’.

Anyway, this references a partial for the table, so lets make a new partial view for the table: “/app/views/entries/_entries.rhtml” (or html.erb) :
<table border=”0″ cellspacing=”0″>
<tr font-weight=”bold”>
<% column_names.each do |width, name| %>
<td width=”<%= width %>%” class=”<%= cycle(‘table-column-header-odd’,
‘table-column-header-even’, :name => “stripes”) %>”> <%= name %> </td>
<% end %>
<% reset_cycle(“stripes”) %>
</tr>

<% for entry in entries %>
<tr>
<%# use the column_data helper to get the code for the cells %>
<% column_data(entry).each do |cell| %>
<td class=”<%= cycle(‘table-column-odd’, ‘table-column-even’,
:name => “stripes”) %>”> <%= cell %> </td>
<% end %>
<% reset_cycle(“stripes”) %>
</tr>
<% end %>
</table>

This builds the table with the use of helpers, as mentioned earlier. The reason for this is two-fold:

  • There’s a lot of ruby code to populate the columns with correctly formatted data. Try to avoid filling up your views with ruby code, as it scares web designers, who we want to feel relaxed (and therefore creative) when they prettify our pages. Much nicer to hide it away inside a helper.
  • To achieve a striped columns effect, i’m using a rails helper called ‘cycle’, which simply cycles between a set of objects it’s given, whenever it’s called. (i don’t know how it keeps track of which one it did last…some magic). In this case, the objects it’s cycling between are some strings, which happen to be the names of styles in my stylesheet (i’ll get to those soon). Each style simply specifies a different background colour. As you can see, the call to ‘cycle’ is a bit cumbersome, and if i had it before every <td> element in a row the view would get spectacularly ugly very quickly. So, instead, i iterate through the an array of chunks of code, which is what the helper provides, and only have to write the cycle call out twice – once for the header row, and once for the record rows. Note with ‘cycle’ that you want to reset it at the end of each row, otherwise if you have an odd number of columns you’ll get a checkerboard effect. That’s why we name the cycle, so we can pass the name to the reset_cycle helper.

So, here’s the helpers used to populate the table: they live in app/helper/entries_helper.rb, which scaffold has kindly provided you with. First, the one to do the header row. This returns an array of arrays, with each subarray holding the width of the column (in percent) and its title.

#returns the width and names of the columns
def column_names
[[28, "Title"],
[28, "Body"],
[17, "Created"],
[17, "Updated"],
[5, " "], #create a column for ‘edit’
[5, " "] ] #create column for ‘delete’
end

Then, back in the partial, i call this method, get the array, and iterate through, passing the members of the subarray through as local variables to be substituted into td attributes.

column_names.each do |width, name|

The other partial works in the same way, except that there’s no subarrays – it’s just a simple array of pieces of ruby code, all bracketed up to avoid confusion. The partial takes the array, and iterates through it to get each bit of code to use to populate the relevant column. I’ll print it in full and then go through line by line, since there’s some restful stuff happening.

# Returns the pieces of code used to populate the entries index table columns
def column_data(obj)
[(link_to truncate((h obj.title), 35), obj),
(truncate((h obj.body), 35)),
("#{time_ago_in_words(obj.created_at)} ago"),
("#{time_ago_in_words(obj.updated_at)} ago"),
(link_to 'Edit', edit_entry_path(obj)),
(link_to 'Delete', entry_path(obj), :method => :delete ) ]
end

Now lets break this down line by line.

def column_data(obj)
‘obj’ is the object passed through from the partial – in this case, an ‘Entry’ object, from the “for entry in entries” line in the partial. So, whenever we see ‘obj’ it’s as if we’re saying ‘entry’. I know that seems obvious but some of the rest calls might be a bit unfamiliar and it’s easier to understand if you mentally substitute ‘entry’ in there.

[(link_to truncate((h obj.title), 35), obj),
link_to tells us this is a link - in this case, if someone clicks on the title i want them to be taken to the 'show' page for that entry. All that's required for 'show' is to pass through the object to be shown - rails assumes that it's an entry since we don't ask for a specific path.

truncate simply truncates the text to the specified number of chars and puts an ellipsis "..." at the end.

We say (h obj.title) instead of simply (obj.title) as a security measure. h is a helper which treats text as raw text, rather than trying to use it as html, which the browser might otherwise try to do. This is a simple security measure - it means that if (for example) someone enters a ton of javascript as their blog post in order to hack our site, then all that happens is that they have a blog post full of javascript code - it doesn't run it. At least, that's my understanding of it :)
(truncate((h obj.body), 35)),
("#{time_ago_in_words(obj.created_at)} ago"),
("#{time_ago_in_words(obj.updated_at)} ago"),

These are all pretty simple - time_ago_in_words is just another helper for formatting a time value into text such as "about 1 hour". You need to add " ago" yourself at the end for the desired effect in this case.

(link_to 'Edit', edit_entry_path(obj)),
Again, rather than passsng :action => "edit", :id => obj, as we would in traditional rails, here was just call the relevant path helper with the desired object.

(link_to 'Delete', entry_path(obj), :method => :delete ) ]
This is more RESTful stuff – notice the “:method => :delete” at the end? This tells the browser to send a DELETE request (well, actually a pseudo-delete request since browsers aren’t allowed to send delete requests. Rails understands :) For the purposes of *our* understanding of REST, think of it as an http DELETE request. When the controller gets a delete request, and is given the path to an object (which will equate to /entries/obj_id), then it calls the ‘destroy’ action on that object, in the relevant controller – entry_controller in this case, since we give it ‘entry_path’. The scaffold provided us with a perfectly working destroy action in our controller, so we don’t need to worry about it any more (for now).

end

Finally, before i forget, let’s get the styles in there. Make a new stylesheet in /public/stylesheets called ‘ajaxblog.css’, and put the following styles into it -

.table-column-even {
background: #f0f0f0;
}

.table-column-odd {
background: #f8f8f8;
}

.table-column-header-even {
font-weight: bold;
background: #f0f0f0;
}

.table-column-header-odd {
font-weight: bold;
background: #f8f8f8;
}

(i know next to nothing about stylesheets, and i think this isn’t the official way to set them up. But hey ho it works.)

And of course, we need to make sure that our layout knows about the new stylesheet. So, go and open app/views/layouts/entries.rhtml, and add this line in the head element, next to the scaffold stylesheet reference -

<%= stylesheet_link_tag “ajaxblog”, :media => “all” %>

While you’re there, add this line as well, below the css references in the head. We’ll need this for our ajax later but since we’re here now we might as well -

<%= javascript_include_tag :defaults %>

OK! That was quite a lot of stuff to do in a single iteration, so go and check that it all looks ok. Table nice and stripy? (you can change the colours in the stylesheet). Delete and edit work? Adding a new entry takes you back to the index? Good.

So, now that we’ve got our restful app working, let’s do some ajax. We’re going to be changing the functionality of ‘delete’, so that it just updates the table, rather than reloads the page. But how can we tell the difference? It’s hard in this case, with such a simple app, because the page will tend to reload so quickly that we might not even see it happen. So, let’s add the time to the page – that way, if the page is reloaded, the time will change. (this might not be the best demo of ajax but it sure is simple)

In app/views/entries/index.rhtml, add the following, below the <h1> line -

<span>Current time: <%= Time.now.strftime(“%H:%M:%S”) %> </span>
<br/><br/>

This just shows the local system time in 12:34:56 format. Try refreshing the browser a few times to check it works. So, we want to update the table without changing this value. Let’s (FINALLY) do some ajax.

First thing to do is to change what happens when the user clicks on the ‘delete’ link. We need to change it to make a remote ajax call. This involves replacing the link_to helper with another helper, called “link_to_remote”. Here’s the old line which, remember, is hidden away in entries_helper.rb:

link_to ‘Delete’, entry_path(obj), :method => :delete

And here’s the new one -

link_to_remote ‘delete’, :url => entry_path(obj), :method => :delete

As you can see, it’s not radically different. Instead of passing entry_path, we point :url at entry_path. But the link_to_remote means that we’ve got an ajax call happening.

Next, we need to go and look at the destroy method in entries_controller, which is called by a DELETE request on an entry object, remember. This is what it looks like at the moment:

# DELETE /entries/1
# DELETE /entries/1.xml
def destroy
@entry = Entry.find(params[:id])
@entry.destroy

respond_to do |format|
format.html { redirect_to(entries_url) }
format.xml { head :ok }
end
end

If you’re new to REST, this will look quite unfamilar once we get to the “respond_to” line. I’ll get to that in a minute.

First, we need to change the controller to provide an updated value for @entries, which we’re going to pass through to the updated table in a minute. So, after the line that destroys @entry, we update @entries:

@entries = Entry.find(:all)

Now, the respond_to block – what’s happening here is that we specify what data we can supply in response to various formats of request. I’m not getting into the xml here – let’s just worry about the normal case, when html is requested. What the controller would do in that case is call the format.html line, which has a block of code that simply redirects back to the index page, ie reloads the page.

If we got an ajax request, we don’t want to redirect – we’re planning on reloading just the table. So we need to stop it from redirecting if we got an ajax request. The way to do this is simple – change the line as follows:

format.html { redirect_to(entries_url) unless request.xhr? }

Our final change is to make sure that when we fall out the bottom of the controller (as we will do now that we no longer redirect anywhere), rails looks for some javascript to run. (at least, that’s my understanding, i could be wrong). So, at the bottom of the list of formats, just add

format.js

So, our new destroy action looks like this:
# DELETE /entries/1
# DELETE /entries/1.xml
def destroy
@entry = Entry.find(params[:id])
@entry.destroy
@entries = Entry.find(:all)

respond_to do |format|
format.html { redirect_to(entries_url) unless request.xhr? }
format.xml { head :ok }
format.js
end
end

The “request.xhr?” part will return true if the action was called by an ajax request. In this case, if the request isn’t an ajax request, we deal with updating the table the old fashioned way – by simply reloading the whole page with a redirect_to call.  Putting this test, using request.xhr?,  into our ajaxy actions is a good habit to get into, as it allows you to deal with the case where the user doesn’t have javascript enabled in their browser, and to do it another way.  This is a good reason, when planning on writing some ajax, to get the functionality working first using the old-fashioned page reloading.  That way, it’s easy to fall back onto the non-ajax functionality if the user can’t, or doesn’t want to, use javascript.

However, if it is an ajax call, then nothing happens, and we just drop out of the bottom of the method. Now what?

This is where the really clever stuff comes in. Rails has a load of javascript helpers that help us to use ajax without actually using the required javascript. They go into rjs (ruby javascript) files, that live in the view folder corresponding to the controller holding the action, and are named the same as the action. An, because we added format.js to the bottom of the respond_to block in the controller, when we fall out the bottom of the controller method rails will look for an rjs file called “destroy.rjs”.

So, in /app/views/entries, create a new file called “destroy.rjs”, and add to it the following line:

page.replace_html ‘entry_table’, :partial => “entries”, :locals => {:entries => @entries}

Let’s see what’s going on here -

  • “page” is an object provided to us by rails to work on. It corresponds to a page of html, from which we pull out individual components, identified by their “id” attribute.
  • replace_html is one of those nice javascript helpers i mentioned. It replaces the element with the id that follows (“entry_table”, which is what we labelled the div that surrounds the call to the table partial, in index.rhtml)
  • So, what do we replace it with? We replace it with a partial, by specifying :partial. Generally i believe it’s the common practise to use ajax to replace a whole partial rather than bits and bobs here and there. It might even be essential (i’m new to this myself and not sure). The partial is of course “entries”, which contains the table. So, we’re saying ‘replace the table with an updated version of the table’.
  • Finally, we pass through to the partial the same parameters that we passed through when we call it from the index page: “:locals => {:entries => @entries}”. In this case, @entries has been updated by the ‘destroy’ method in the controller, and now no longer contains the record we just deleted.

The end result of all this is that the partial with the table gets reloaded, with a new value of @entries to dutifully iterate through, and we see our deleted record disappear, without the page being reloaded (make sure the time doesn’t change!).

So there you go. Add ajax and RESTful rails to your skills list and go and make a celebratory cup of tea. Of course, i’ve barely grazed the iceberg of ajax or REST here, so go and read some more about both of them while you drink it.

About these ads

2 Responses to “Super-simple blog app with Ajax and Restful Rails (Rails 2.0)”

  1. Marcel Says:

    Thanks for that nice intro to ajax and rails 2.0.

  2. sandrar Says:

    Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: