There are a bunch of tutorials online for implementing ajax functionality in Rails. It makes for a rather nice user experience, and provides a bit of a performance benefit from the perspective of the user. I know I enjoy using websites that provide this functionality. I pieced together the functionality that I wanted from a number of tutorials and the Rails guides, and I wanted to create a guide to show how I implemented them.
The code for this will be demonstrated in a minimal Rails app. Check it out on Github. For reference, I’ll be using the following things:
If you’re viewing this from the future, this will hopefully make things more sensible for you.
Create a new Rails app, setup your RVM/rbenv/chruby/etc as you like it, and we’re set. In the tradition of basic Rails tutorials, this will be a blog with a Post.
$ rails generate scaffold Post title:string body:text $ rake db:migrate
Now we have the default MVC for Posts. Fantastic. Let’s make it work ajax-style.
Here’s how I want it to work:
respond_to call in the
new action. It’ll look like this:
The controller will now respond to “script” requests with the
app/views/posts/new.js.erb by convention.
To request this, it’s super simple. Edit the
index.html.erb file and add
remote: true to the
link_to for New posts.
Clicking “New Post” doesn’t do anything, because we don’t have a
new.js.erb file yet. Create
app/views/posts/new.js.erb with the following:
Now, clicking the New link causes an alert to show up. That’s all well and good, but we want to make a new post, not annoy users with
Our first step is to render the form.
render 'form'. Easy, right? Rails provides a method
j for convenience. We’ll then wrap that all up in a nice little jQuery object for convenience. We’ll create a container
div to wrap it, append the whole thing to the body, and be set. The result:
Not too bad. Clicking “New Post” now slaps the form into the page. Of course, you can click “New Post” multiple times and it just continues appending forms willy-nilly.
We want the form to submit over ajax as well, so edit the
form_for method in the
_form.html.erb and add
This won’t work, because we haven’t setup the create action yet.
As above, all we need to do is add
format.js into the
respond_to block for both branches of the if statement. This tells the server to respond with
Alright, so what do we want to do?
We’ll select the old form’s div, then enter the check to see if there are errors. If there are, then we’ll replace the div’s html with the re-rendered form. If not, we’ll render the post partial and insert it into the table.
Actually running this will now cause an error, as the post partial hadn’t been made yet. Silly Rails scaffold! Let’s refactor the index page a bit. Replace the
<% @posts.each do |post| %> loop with the following:
Rails is clever enough to render each post using the partial, and we got some reuse in our script. New posts and creating posts now works as you’d want!
There are some neat benefits to this:
You can do a
<%= link_to 'New Post', new_post_path, remote: true %> anywhere on your site and it will inject the form for you. That might not be great for a
Post, but for a
ReportContent or other model that would be great.
Rails only needs to render much smaller objects for these, rather than a full application page. This is a good bit faster, both for your server and the client.
If you just want limited ajax for a few models, then this works great and is rather easy to setup.
It’s not perfect, though:
Yeah, you’re now having to keep track of two views for new/create, one of which is injecting content int your page. Does your site even have a place for it?
This is a middle-ground solution. It’s sitting somewhere inbetween a full-HTML solution and a single page application. If you were just sending JSON back and forth, then you’d have much easier time managing the view.
If you want to do something more complicated, this can get real tricky fast.
remoteparameter to my
_form.html.erbobject that would default to false unless passed. Forms rendered by default work over html, but the code
<%= render 'form', remote: true %>makes it ajax.