Key Takeaways
- Volt is a Ruby web framework that simplifies the connection between client and server, and it offers a unique way of managing assets, components, and routes. Despite being in its early stages, Volt’s asset management is quite similar to Rails, allowing developers to add CSS and other assets to their applications.
- Components in Volt serve as a way to divide your application into smaller parts, with all files for a particular component loaded as soon as the first page within that component is accessed. This makes components act as “reload boundaries” within your app, enhancing the performance and organization of your application.
- Routes in Volt function in a “live” fashion due to data syncing over Websockets. Unlike traditional web frameworks, the URL in Volt does not control the application’s state but rather serves as a reference to a specific state. This blurs the distinction between client and server, offering a more dynamic and responsive user experience.
Volt is an awesome new Ruby web framework that aims to reduce the barrier between the client and server (a bit like Meteor, but in Ruby). Last time around, we uncovered some of the basics of Volt. You can check out the code right here. Here, we’ll build on the bookmarking app and go through asset management, components and routing, the Volt way. Even if you haven’t read the last article in its entirety, it should be easy to catch on.
Let’s start off with the simple bookmarking page we built last time with Volt. Here’s what it looks like right now:
That doesn’t really appeal to my eyes and it’s a bit hard to use. To make it look a bit better, we should be able to link in some CSS.
Assets
Volt’s asset management is a bit primitive and the terse-at-best documentation page on the topic notes that “asset management is still early, and likely will change quite a bit.” Fortunately, we can still do the basic stuff we need to, e.g. add some CSS. Before we get to that, let’s change the markup so styling the page is a bit easier. Open up config/base/index.html and change it to the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<% javascript_files.each do |javascript_file| %>
<script src="<%= javascript_file %>"></script>
<% end %>
<% css_files.each do |css_file| %>
<link href="<%= css_file %>" media="all" rel="stylesheet" type="text/css" />
<% end %>
<link href='http://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
</head>
<body>
</body>
</html>
Really, the only thing we’ve added is a Google Web Font with this line:
<link href='http://fonts.googleapis.com/css?family=Oswald' rel='stylesheet' type='text/css'>
That’ll just let us use the font from our CSS to make the page look a little bit better. We need to make a few more changes in app/main/views/main/bookmarks.html in order to get a hold of the page elements using CSS selectors:
<:Title>
Bookmarks
<:Body>
<form e-submit="add_bookmark" role="form">
<div class="form-group">
<label class="muted">New Bookmark</label>
<div class="form-element">
<span class="input_label">Description</span><input class="form-control" type='text' value="{{ page._new_bookmark._description }}" />
</div>
<div class="form-element">
<span class="input_label">URL</span><input class="form-control" type="text" value="{{ page._new_bookmark._url }}" />
<input type="submit" value="Add Bookmark" />
</div>
</div>
</form>
<div id="bookmarks">
<h1 id="bookmark_header">Bookmarks</h1>
{{ _bookmarks.each do |bookmark| }}
<a class="bookmark" href="{{ bookmark._url }}">{{ bookmark._description }}</a>
{{ end }}
</ul>
</div>
A quick refresher about the meat of this code might be helpful. There are really only two parts that are special. First of all, we have the form input:
<input class="form-control" type="text" value="{{ page._new_bookmark._url }}" />
Here, we’re defining a binding page._new_bookmark._url
so that we can access that value in the controller and expect it to hold the value of the text field. The other important part is the bookmark display:
{{ _bookmarks.each do |bookmark| }}
<a class="bookmark" href="{{ bookmark._url }}">{{ bookmark._description }}</a>
{{ end }}
If you’ve used Rails before, this type of code shouldn’t come as a surprise. Basically, we’re looping over the bookmarks and then creating links for each of them. However, Volt makes this special, because we’re actually creating bindings for bookmark._url
and bookmark._description
which are from the bookmark object constructed from the database. Alright, so we have the basic display working, but it looks pretty bad. Let’s spruce it up with some CSS.
Asset management in Volt is still in its early stages but if you come from Rails, you’ll feel at home with it. Assets are divded up by components. Right now, we only have one component and it’s called “main”. Volt links in your assets for you. What exactly does it link in? As the documentation notes: “Anything placed inside of a components asset/js or assets/css folder is served at /assets/{js,css} (via Sprockets).” Volt, like Rails, also uses Sprockets in to pre-compile and manage assets.
We’ll do a simple dual-color (with a few shades) scheme for the bookmarks page. Let’s first get some colors down as SCSS variables so we don’t have to keep repeating them. Create the file app/main/assets/variables.css.scss and slam down the colors:
$background-color: #EFECCA;
$muted-color: #A7A37E;
$accent-color: #046380;
$muted-accent: #CDE0E5;
$text-field: #FAF8E4;
We have some CSS directives that can be applied across the entire component. Stuff those inside app/main/assets/css/app.css.scss:
@import 'variables';
body {
background-color: $background-color;
}
.header {
border-bottom: 5px solid $accent-color;
}
.header h3 {
font-family: Oswald;
color: $accent-color;
}
.header li a, .header li:hover a {
background-color: $muted-color;
border-radius: 0px;
color: #fff;
font-weight: bold;
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
.header li:hover a {
background-color: $accent-color;
}
.muted {
color: $muted-color;
}
The CSS itself is pretty simple, as it is just setting colors and fonts. What is important, however, is the fact that we can @import
other SCSS files. If you’re from Rails-land, you might be expecting the app.css.scss
to be converted into app.css
and hold all of your CSS in one place. However, Volt doesn’t have asset bundling yet, so sit tight for that to come along.
We have some of bookmarks-specific CSS, which can go in app/main/assets/css/bookmarks.css.scss:
@import 'variables';
.form-group .muted {
font-size: 14px;
text-transform: uppercase;
font-weight: normal;
letter-spacing: 1px;
}
.form-group input[type="text"] {
border: 1px solid $muted-color;
border-radius: 0px;
box-shadow: none;
margin-bottom: 15px;
background-color: $text-field;
}
.form-group input[type="submit"] {
background-color: $accent-color;
border: none;
color: $muted-accent;
font-size: 14px;
padding: 15px;
text-transform: uppercase;
font-weight: bold;
letter-spacing: 1px;
}
.form-group .input_label {
background-color: $accent-color;
color: #fff;
padding: 5px;
}
.form-element {
margin-top: 5px;
}
#bookmarks {
margin-top: 55px;
}
#bookmark_header {
text-align: center;
font-weight: bold;
text-transform: uppercase;
font-size: 20px;
color: $accent-color;
margin-bottom: 35px;
}
#bookmarks ul li {
list-style-type: none;
}
.bookmark {
background-color: $accent-color;
color: #fff;
padding: 15px;
margin-top: 3px;
display: inline-block;
}
.bookmark:hover {
color: #fff;
text-decoration: none;
}
Here’s what our page should now look like:
Remember that just naming the file bookmarks.css.scss doesn’t actually do anything. Volt would have included it even if we named it “treefrogs.css.scss”. Volt, frankly, doesn’t give two hoots about the name of the file. But, keeping your CSS somewhat separated might help in case you want to separate a part of your code into a different component. Another way to think about it is: If you have to really try to keep your CSS from getting all tangled, you should probably split into another component. By the way, what exactly is a component?
Components
Components aren’t just what you expect in Volt. First, they function as a way to divide up your application into little pieces. But, Volt also loads all of the files for a particular component as soon as you load up the first page within a component. The point of doing this is that if there is a component of your website called sales
, we can say with reasonable confidence that once a user accesses a route within the sales
component, that user is pretty likely to go to another route within the same component. So, the documentation advises us to think about components as “reload boundaries” within your app.
We can generate a component easily, as Volt gives us with a simple generator that will setup the directory structure and a few files.
volt generate component bookmarks
The output of the command gives you an overview of what we mean when we say “Volt component”:
create app/bookmarks
create app/bookmarks/assets/css
create app/bookmarks/assets/js
create app/bookmarks/config/dependencies.rb
create app/bookmarks/config/routes.rb
create app/bookmarks/controllers/main_controller.rb
create app/bookmarks/models
create app/bookmarks/tasks
create app/bookmarks/views/index/index.html
Basically, the controllers, models, views, and assets are all separated by component. Since what the bookmarks app has right now is so simple, there isn’t much point moving it into a separate component. We’ll just stick with the main component (i.e. you can safely delete app/bookmarks).
We can separate our application by these “components” and each component has some routes associated with it. Although we’ve seen the routes in action before, we haven’t really understood how they work in Volt.
Routes
Routes are the lifeblood of most web frameworks because they tell us where we are in an application at any given point in time. But, because Volt does all this data syncing over Web sockets, the URL works in a somewhat “live” fashion. Say we load a page with the route “/people/dhaivat” where “dhaivat” is the name of one of the “people”. If, in the controller we end up changing the model value associated with “dhaivat”, then the URL will change on the client! Fundamentally, the URL doesn’t control the application’s state; the URL is just a way to refer to a specific state.
What can we actually do with routes then? We know that we have to set them inside app/
get '/bookmarks', _action: 'bookmarks'
This just maps the “/bookmarks” route to the “bookmarks” action in controllers/main_controller.rb
. Turns out we can have bindings on routes just as we do for markup:
get "/bookmarks/{{ _bookmark_id }}", _action: 'show_bookmark'
With this route, if we visit the url “/bookmarks/16”, params._bookmark_id
will contain 16
. Then, we can change that value in the controller and the URL on the client will update to reflect that change. This, again, is one of the places where Volt tries to blur the distinction between client and server.
Wrapping It Up
If you’re coming from a traditional Rails background, Volt might seem a bit strange, at first. There are some rough edges within Volt still (e.g. no asset bundling), but as those continue to melt away, Volt is a fantastic framework for a web app where the client-side is just as (if not more) important than the server-side. Hopefully, this article will help you along your way to write your next app in Volt.
I'm a developer, math enthusiast and student.