Illustrating the Power of Rails
So what are the tricks to making this darn thing in Rails?
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.
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.
# 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()
Killing Two Birds with One Stone
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.
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.
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.