Matt De Leon

Matt De Leon

Programmer · Designer · Jiu-Jitsu Blue Belt · Recreational Pilot · Homebrewer

The TodoMVC Project on Rails 4

Summary: I built the TodoMVC app according to specification using only Rails 4 and a hint of javascript. Check out the app here and the code here.

Another Javascript MVC framework will be born on GitHub by the time you finish reading this sentence.

When I started learning Rails a year and a half ago, I was already surfing the Javascript MVC wave. I had built a few apps in Backbone.js and was excited to combine my Backbone experience with the power of Rails to enhance my apps further.

Fast forward a year and a half, and I now rely almost exclusively on the default Rails stack to build interactive web apps--no Javascript MVC framework required. How is this done, and why do I think this is a better approach?

Illustrating the Power of Rails

The TodoMVC project contains dozens of Javascript frameworks and their implementations of the TodoMVC spec. As such it's the perfect app to replicate in Rails and illustrate the power of the standard Rails stack.

I believe the project was a success, but don't take my word for it. Check out the source code here on GitHub. I'm also hosting a version of the app on Heroku.

So what are the tricks to making this darn thing in Rails?

Tool #1: RJS (Rails Javascript)

RJS is simply the practice of returning Javascript from a web request and eval'ing it. And it's the key to adding, removing, and changing content on a page when an action occurs server-side.

Take adding a todo as an example:

# todos_controller.rb def create @todo = Todo.new(todo_params) @todo.save end # todos/create.js.erb $("#todo-list").append("<%= j(render(@todo)) %>"); $("#new-todo").val("");

In just a few lines of code, the real power of RJS comes alive: we're dynamically adding a todo to the page while making use of a typical Rails controller and view (not shown, but it would be the _todo.html.erb partial). We also do another small job: reset the new todo textbox. Check out the code to see more examples of RJS views.

To those familiar with Rails, this talk of RJS may sound patronizing. But then why do so many of us jump on the Backbone bandwagon? In the Backbone world, you'll need to understand concepts like collections, events, and views, choose a templating language, and prepare for a subpar in-browser debugging experience, amongst other issues. If nothing else, there is a learning curve, one that I believe is steeper than learning these few tricks.

Tool #2: Remote forms and links

Cool cool, but how do we get one of these RJS views to render? Rails UJS comes included in Rails and supports adding a data-remote attribute to links and forms to submit them via ajax.

Back to the new todo form:

# todos/index.html.erb <%= form_for(Todo.new, remote: true) do |f| %> <%= f.text_field :title, placeholder: "What needs to be done?" %> <% end %>

Nothing too out of the ordinary, but note the remote: true option that adds data-remote="true" to the form element. When the form is submitted, Rails UJS intercepts the submit and sends the request via ajax. Another example with a non-CRUD action, toggling a todo:

# todos/_todo.html.erb <%= form_for(todo, url: toggle_todo_path(todo), method: :post, remote: true) do |f| %> <%= f.check_box :completed, class: "toggle", "data-behavior" => "submit_on_check" %> <% end %>

I use and abuse remote forms and links. It's an incredibly simple way to take your app from stoneage page-refreshing to slick and interactive.

Tool #3: .js.erb Layouts

Ok, it can't all be straightforward. One challenge was dealing with the many elements that change in the todo list's footer.

The number of items left and the "Clear Completed" button both require updating when todos are added, completed, and removed. Instead of copying the same code to update these elements across many .js.erb views, I opted to create a .js.erb layout.

# layouts/todos.js.erb <%= yield %> $("#footer").replaceWith("<%= j(render("footer")) %>");

yield spits out the regular .js.erb view before continuing to re-render the footer.

This technique felt awkward at first, but it's a lot like an html layout in that it wraps the .js.erb views with Javascript to execute on every action. In fact, I've now used this technique on another project. It appears to be a useful technique when a part of the DOM needs updating across several actions.

Tool #4: Turbolinks

The todo app needs links to filter the todos by those completed and not completed. A fun twist to the requirement is that the browser's back & forward buttons need to work when filtering.

Turbolinks to the rescue.

Turbolinks makes transitions between pages feel snappy and is a default in Rails 4. This means we can link to "todos/active" and "todos/completed" and expect uber-fast filtering, all with the browser's page history intact.

Turbolinks works out-of-the-box but requires some discipline in writing Javascript. Check out the Turbolinks Railscasts for more.

Tool #5: Sprinkles of Javascript

Avoiding a Javascript MVC framework does not mean Javascript isn't required to build web software. Quite the contrary. To make its UI come to life, the TodoMVC app needs 70 lines of coffeescript in addition to the snippets of RJS code.

But compared to the Javascript required to build a Backbone, Angular, or Ember app, the complexity of the Javascript here is relatively minor. The code is basic jQuery with a hint of advanced Javascript to handle editing a todo.

Typical Javascript I write looks similar to this snippet, which submits the new todo form "on enter":

# todos/index.html.erb <%= f.text_field :title, "data-behavior" => "submit_on_enter" %> # todos.js.coffee $(document).on "keypress", "[data-behavior~=submit_on_enter]", (e) -> if e.keyCode is 13 $(@).closest("form").submit() if $(@).val().trim().length e.preventDefault()

For me, the key is to think of Javascript as adding additional functionality to a web page and not seek to make it responsible for the whole app. Employing a Javascript framework to build an entire app is a lot like asking an interior designer to build a entire house.

Killing Two Birds with One Stone

A subtle but important side-effect to how the todo app is written is the inclusion of persistence: our todos are saved to a database. The Javascript implementations of the app do not include this feature.

Let's say we had written the TodoMVC app in Backbone and wanted to release it for practical use. That might mean enabling people to access their todos from their laptops, cell phones, and iPads. As a result, we'd need to write a backend, maybe in Rails, to save people's todos.

But by sticking to Rails from the get-go, we've accomplished this important slice of functionality. And the programmers at "Todos Inc." are happy that they're not dealing with the additional mental overhead of a Javascript framework.

Embracing Constraints

Inevitably, there are tasks that are difficult to accomplish with Rails that may be better suited for a Javascript framework. I wouldn't try to build Google+ without the support of a Javascript framework.

Yes, Rails has constraints. But constraints are a part of reality. In the world of mobile devices, software and hardware engineers live with the constraint of limited (and short) battery life. Nobody wants to lug around a 10 pound iPad.

Similarly, we could utilize a Javascript MVC framework within our Rails apps to enable additional functionality, but do we really want the additional mental overhead? Alternatively, we can design and build our apps within a constraint: the default Rails stack.

The most painful part of the TodoMVC app was updating the footer whenever a todo was added, completed, or removed. While I converged on a solution using .js.erb layouts, in the real world I would've worked to reduce this complexity by exploring different designs. And that's ok: there's almost certainly a different design that is equally as useful but easier to build.

Rails is simple, built for the job, and enjoyable to work with. Embrace that beautiful constraint. Don't be caught carrying a 10 pound iPad.

Conclusion

The irony of all of this is that I love programming in Javascript. But several Backbone apps and many lines of coffeescript later, I'm one-fifth the Javascript programmer that I am the Ruby programmer. It seems clear as day that Ruby and Rails are more conducive to building whole web apps.

Now, there will be times when a tool like Backbone is the best choice. 37signals (the creators of Rails) used Backbone to develop Basecamp's calendar. But most of us are not building highly interactive calendars. And even fewer examples exist that require whole apps to be written in Javascript.

So next time you consider using a Javascript MVC framework, remember that you have an alternative, one with a track-record and one that is easier to learn, program with, and maintain: Rails itself.