Real-time Web Apps with Volt in Ruby

Share this article

volt

Volt is a slick, new Ruby web framework that aims to blur the line between client and server code. The basic idea behind the framework is that you can write your client-side code (which is usually Javascript) in Ruby using Opal, a Ruby runtime within Javascript. In addition, Volt provides some nice ways to relay data between the client-side and the server-side. If you’ve used Meteor before, Volt is a very similar idea, but there are many portions of Meteor which Volt doesn’t have. I think Volt has some real potential. As web apps become more and more client-side heavy, it is a pain to have to switch mental context between Javascript and Ruby. It’s even more of a pain to figure out how to flow simple pieces of data between the client and server. Volt can help you get there quickly.

In this article, I’ll go through how to build an incredibly simple bookmark “app” with Volt. The point of this article is to get you up to speed with some of the very basics and to get you a feel for how the client/server divide works in Volt. Let’s get to it.

Views

First of all, we need a copy of Volt:

gem install volt

If you’ve used Rails before, then you’ll find some of the “volt” command line arguments pretty familiar. Here’s how we create a new project:

volt new volt-example

You should see a familiar-looking (if you’re a Rails kind of person) directory layout:

Gemfile
Gemfile.lock
README.md
app
config
config.ru
lib
spec

To see what Volt has already set up for us, fire up the web server:

bundle exec volt server

You should see something like this:

Volt Web Server

Alright, so where is this page generated, exactly? If you go into the app directory, you should see a “main” directory. Unlike Rails, Volt lets you break your webapp up into “components” (this idea is similar to the Django style of separation). It automatically defines the “main” component for you, so let’s head into that directory. You now see:

assets
config
controllers
models
tasks
views

If you peek in views/main, you can see the views associated with this component. The layout for the views that are part of this component is defined in main.html which looks like:

<:Title>
  {{ template main_path, "title", {controller_group: 'main'} }}

<:Body>
  <div class="container">
    <div class="header">
      <ul class="nav nav-pills pull-right">
        <:nav href="/" text="Home" />
        <:nav href="/about" text="About" />
        <:user-templates:menu />
      </ul>
      <h3 class="text-muted">volt-example</h3>
    </div>

    <:volt:notices />

    {{ template main_path, 'body', {controller_group: 'main'} }}

    <div class="footer">
      <p>&copy; Company 2014</p>
    </div>

  </div>

<:Nav>
  <li class="{{ if active_tab? }}active{{ end }}">
    <a href="{{ attrs.href }}">{{ attrs.text }}</a>
  </li>

The odd looking <:body></:body>, <:title></:title>, etc. are a pretty integral part of how views work in Volt. Basically, if you see a tag in the form <:tagname></:tagname> (note the capitalized first letter), it is defining or utilizing a section. In the layout, we are defining three sections: <:title></:title>, <:body></:body> and <:nav></:nav> which include some basic layout and then defer to the template of the action we have called. Specifically, these are the two lines that call the action templates:

{{ template main_path, "title", {controller_group: 'main'} }}
...
{{ template main_path, 'body', {controller_group: 'main'} }}

In Volt, wherever we have some code enclosed by {{</code> and <code>}}, it is executed as Ruby using Opal. In this case, we’re using some of the variables available in order to pull up the template appropriate for the page using this layout. Alright, let’s look at one of these templates. Open up app/main/views/main/about.html:

<:Title>
  About

<:Body>
  <h1>About</h1>

  <p>About page...</p>

That looks pretty simple! Basically, we’re just filling in the sections in the layout. Let’s jump back to the template to examine it a bit more closely. You might notice that some of tags are in the form <:tagname></:tagname> (note the lowercase first letter). These are not sections; they are controls. In a sense, they are like Rails’ template helpers (e.g. link_to) since they help you generate some HTML. We’ll see some more of those as we continue.

Routes and Adding a View

Alright, that’s enough knowledge for the timebeing. Let’s get started building our bookmarking app. We’ll need one page where the user can see the list of bookmarks and add a new one. We’ll deal with the functionality as we move along, but first, create a new view and a route to go with it. Creating a view is simple; just create the file app/main/views/main/bookmarks.html:

<:Title>
  Bookmarks

<:Body>
  <h1>Bookmarks</h1>

To add the route, we go over to config/routes.rb and the following line:

get '/bookmarks', _action: 'bookmarks'

Annnd, that’s it! You’ll have to restart the server (the author of Volt says that soon you won’t have to do this) and then you should see a nice page at localhost:3000/bookmarks. In fact, we can make the bookmarks action the index by changing the last line of config/routes.rb to the following:

get '/', {_action: 'bookmarks'}

Another neat thing about Volt is that it gives you automatic page reloads. If you try changing the contents of app/main/views/main/bookmarks.html and then save the file, you will be able to see the page reload in the browser.

The Basic Pieces

Let’s figure out how to process a form so that we can actually create new bookmarks. Subsequently, we want to display these bookmarks nicely on a page. Before we do that, it is incredibly important to understand a fundamental difference between Volt and Rails. Volt is a sort of MVVM (Model View, View Model)) framework, not an MVC framework. In practical terms, the view in Volt calls on the controller to get stuff done, not the other way around.

First of all, we need a form. That’s pretty easy, just add the following to the <:body></:body> of bookmarks.html:

<form e-submit="add_bookmark" role="form">
    <div class="form-group">
        <label>New Bookmark</label>
        <input class="form-control" type='text' value="{{ page._new_bookmark._description }}" />
        <input class="form-control" type="text" value="{{ page._new_bookmark._url  }}" />
        <input type="submit" value="Add Bookmark" />
    </div>
</form>

Alright, some parts of this code are run-of-the-mill, others aren’t. We have what seems like a simple form element. However, it has some weird attributes, e.g. e-submit. Here, e-submit = "add_bookmark" is telling Volt that the add_bookmark method/action in the controller should be called when data in this form is submitted. The other two weird lines are:

<input class="form-control" type="text" value="{{ page._new_bookmark._description }}" />
<input class="form-control" type="text" value="{{ page._new_bookmark._url }}" />

We are defining text input elements but the magic happens in the value field. Within the value = "..." attribute, we are creating a binding. When the value of those text elements change, the values of the variables page._new_bookmark._description and page._new_bookmark._url will change. At this point, this form will do nothing important since we haven’t really put anything in the add_bookmark controller action. So, open up app/main/controllers/main_controller.rb and the add the following as a method:

def add_bookmark
  page._bookmarks << {url: page._new_bookmark._url, description: page._new_bookmark._description}
  page._new_bookmark._url = 'URL'
  page._new_bookmark._description = 'Description'
end

In Volt, this thing called page is a model. It isn’t a model in the Rails sense of the word – it doesn’t necessarily have anything to do with your database. Instead, a model is a piece of data that the frontend and backend can both get. Remember that Volt is not just a server-side Ruby framework (such as Rails or Sinatra). The point of Volt is that it lets the client-side and server-side interoperate pretty seamlessly.

Back to page. In the form, we’ve bound the variables page._new_bookmark._description and page._new_bookmark._url (notice the underscores prepending the variable names; using those automatically gives us empty models to put data in) to certain form values. In the controller code, we can take these variables and use them! Update them in the view and the changes to those variables are available in the controller. This might not seem all that magical at first glance, but the power of the idea comes from the fact that you could create a model of any kind in the client and then expect it to show up when you call controller code from the view.

The controller then takes page._bookmarks and puts a Ruby hash inside with what represents the data associated with one bookmark in our simple webapp. Before we can see the results of our efforts, we need to have a bit of code in the view that displays these bookmarks:

<ul class="bookmark-list">
  {{ page._bookmarks.each do |bookmark| }}
  <li>
          <a href="{{ bookmark._url }}">{{ bookmark._descr }}</a>
  </li>
  {{ end }}
</ul>

Alrighty, head over to “localhost:3000”, you’ll see a pretty plain looking form where you can enter a description and a URL, hit enter, and see it appear in the list of bookmarks just below.

Persistence

If you refresh your page after inputting some of those bookmarks, you’re in for a nasty suprise: they probably all just vanished. We need some way to persist that information.

Persistence is really easy in Volt, but it does come with a gotcha: Volt currently only supports MongoDB if you want to use the client-server-sharing magic. You might ask why Volt doesn’t support something more “standard”, like Postgres or MySQL. The problem is that translating JSON objects to and from multiple SQL tables is quite a headache and we’d need an ORM in the middle. Essentially, Volt is part of the camp that believes that having that sort of a translationary ORM within a web framework is a bad idea.

If you don’t have a copy of Mongo, it’s pretty easy to get. On Mac OS X:

brew install mongodb
mongod

If you’re on Linux, for our purposes, using the version of Mongo available in your distribution’s package manager should be fine. With Windows, I’ve found this guide helpful.

If you head over to db/config/app.rb, you;ll find the following lines commented out:

config.db_driver = 'mongo'
config.db_name = (config.app_name + '_' + Volt.env.to_s)
config.db_host = 'localhost'
config.db_port = 27017

Just bring them back into existence. We’ll need to create a model for our bookmarks. Since “models” in Volt aren’t really connected to a “CREATE TABLE” SQL command, we don’t really have to specify much. We just have to create the file app/main/models/bookmark.rb and drop in:

class Bookmark < Volt::Model
end

Your database setup is ready to go after a restart of the Volt server. We do, however, need to change our view and controller a bit. Remember the page._bookmarks array we were putting our bookmarks into? In order to make that sync into MongoDB, we just have to change page._bookmarks to _bookmarks. Volt will handle the rest.

Here are the changed lines:

_bookmarks << {url: page._new_bookmark._url, description: page._new_bookmark._description}

{{ _bookmarks.each do |bookmark| }}

That’s it! Notice how easily we are getting data from the browser to Mongo. However, there are a few issues with this approach. This idea of database syncing between the client and server came into fashion with Meteor. However, Meteor takes the client-server connection a step further with a completely client-side implementation of Mongo. In addition, Meteor lets your app listen for changes in collections on the server and get them pushed directly to the client database. This stuff isn’t part of Volt, as far as one can tell from the existing documentation. So, Volt’s persistence support isn’t quite as powerful as that of Meteor when it comes to real-time applications with live data updates.

That’s a Wrap!

We’ve only scratched the very surface of Volt. In fact, our bookmark manager looks a bit…garbage. But, we’ve come far enough to give you a taste of what the basic idea of Volt is: closer and easier integration between the client and server.

The code for this article can be found on Github.

Dhaivat PandyaDhaivat Pandya
View Author

I'm a developer, math enthusiast and student.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week