Introducing Volt, a cutting-edge Ruby framework designed for creating dynamic applications

The Volt framework for Ruby is designed specifically for data-rich applications. Both the server and client sides are written in Ruby, then compiled to Javascript via OPAL. This allows developers to build very dynamic applications without needing to write any Javascript. As a Ruby enthusiast, I find this framework to be very appealing.

Front-end Javascript frameworks like Angular.js, Backbone.js, and Ember.js have gained popularity for their ability to make web applications more dynamic. However, these frameworks are most effective when used with a back-end application, and are often paired with web frameworks like Ruby on Rails and Django.

The Ruby framework Volt, on the other hand, is capable of handling both the back-end and a dynamic front-end. It allows developers to build applications quickly since both functionalities are tightly integrated into its core. Volt is structured similarly to an MVVM architecture and leverages the benefits of data bindings.

One of Volt’s most impressive features is its built-in real-time functionality. Building real-time applications can be quite complex, often involving technologies like AJAX-polling, web sockets, Server-Sent Events (SSE), or even external services, which adds complexity and potential costs. Unlike other frameworks, Volt maintains a persistent connection with the server through web sockets. This eliminates the need for separate Ajax requests for every action, allowing changes to be instantly pushed to all clients without any configuration.

Meet Volt, A Promising Ruby Framework For Dynamic Applications

Building a Chat Application with Volt

This Ruby framework tutorial will guide you through the process of building a real-time application using Volt. We’ll create a chat application, a classic example of real-time functionality, to showcase Volt’s capabilities.

First, let’s install Volt and MongoDB. We won’t cover the MongoDB installation process in detail:

1
2
3
gem install volt
 
brew install mongodb
1
mkdir -p /data/db 

(create dbpath)

1
chown `id -u` /data/db (change the owner to have the proper dbpath permissions)

We can now create our first application, which we’ll name ‘chat’. This can be done with a few simple commands:

1
2
volt new chat
cd chat

The document structure shares similarities with Rails, but Rails users will notice an additional ‘Component’ folder within the ‘app’ folder. This ‘Component’ folder contains other folders such as assets, controllers, models, and views.

A Component is a self-contained section of the app. When you navigate within a Component, all pages render without requiring a full page reload since all necessary files are loaded with the initial HTTP request. Visiting a page in a different Component will trigger a new HTTP request, causing the page to reload. For this example, we will work within the default ‘main’ component.

Let’s start the server by running the command ‘volt server’ in the console and view it in the browser by navigating to localhost:3000:

1
volt server

Don’t forget to start MongoDB in the console as well:

1
mongod

Volt provides several default pages, including ‘Home’ and ‘About’, which you can customize. You’ll also notice a login button in the top right corner. Volt integrates user functionality through the ‘volt-user-templates’ gem, which provides out-of-the-box user registration and authentication.

Getting Started

Let’s begin building our application. We won’t be needing the ‘About’ page, so we can delete the following: the file located at ‘app/main/views/main/about.html’, the about action in ‘app/main/controllers/main_controller.rb’, the ‘/about’ route in ‘app/main/config/routes.rb’, and the corresponding nav link in ‘app/main/views/main/main.html’.

1
2
3
4
<ul class="nav nav-pills pull-right">
  <:nav href="/" text="Home" />
  <:user-templates:menu />
</ul>

Now, let’s display a list of all registered users:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<:Body>
  <h1>Home</h1>
  <div class="row">
	<div class="col-md-4">
  	{{ _users.each do |user| }}
    	<div class="contact">
      	{{user._name}}
    	</div>
  	{{ end }}
	</div>
  </div>

This code will list all registered users on the homepage. Notice that the code within the double curly braces {{ }} is Ruby code that gets executed, allowing us to iterate through the user collection and display each user.

The ‘users’ variable represents the collection containing all users. It’s important to note that attributes are accessed by prefixing them with an underscore ‘_’. To enable this, we need to add a line of code to the top of the ‘main_controller.rb’ file:

1
model :store

Volt provides several collection models accessible from the controller, each storing information in a different location. The ‘store’ collection model saves data in the data store, and we’re instructing the controller to utilize it (currently, MongoDB is the only supported data store). Now, let’s create a few users to see how it looks.

Currently, the page simply lists registered users. Let’s enhance this by allowing users to select another user to message, hiding the logged-in user from the list (as they can’t message themselves), displaying the list only to authenticated users, and showing a ’landing’ page to non-authenticated users:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<:Body>
  <h1>Home</h1>
  {{ if Volt.user }}
	<div class="row">
  	<div class="col-md-4">
    	{{ _users.each do |user| }}
      	{{ if user._id != Volt.user._id }}
        	<div class="contact {{ if params._user_id == user._id }} active {{ end }}" e-click="select_conversation(user)">
          	{{user._name}}
        	</div>
      	{{ end }}
    	{{ end }}
  	</div>
	</div>
  {{ else }}
	<p>This is a sample application built with Volt to demonstrate its real-time capabilities. Please log in to access it.</p>
  {{ end }}

Volt.user retrieves the current logged-in user or returns nil if no user is logged in.

The ’e-click’ attribute enables us to bind a specific method from the controller to be executed when the element is clicked.

Attributes And CSS

All attributes prefixed with ’e-’ act as event binders in Volt. For example, ’e-submit’ can be added to a form to define the action triggered on the controller upon form submission. We’ll add the selected user’s ID to the parameters to identify the selected user and apply an ‘active’ class for styling purposes.

Now, let’s create the ‘select_conversation’ method within the controller:

1
2
3
def select_conversation(user)
  params._user_id = user._id
end

That’s it! If you refresh the page, you’ll notice the URL updates each time you click on a user’s name. Additionally, the ‘active’ class is being added to the element. Let’s add some CSS to make this visually apparent (we’ll include CSS for elements we’ll add later):

1
2
3
4
5
6
7
.conversation{
  form{
	input{
  	margin: 10px 0 5px 0;
	}
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.contact{
  width:100%;
  padding:5px;
  margin: 4px 0;
  font-size:15px;
  cursor:pointer;
  &:hover{
	background-color: #FAFAFA;
  }
  &.active{
	background-color: #337ab7;
	color: #FFF;
  }
  .badge{
	background-color: #900;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.message{
  max-width: 80%;
  padding:10px 15px;
  margin: 5px 0;
  background-color: #FEFEFE;
  border: 1px solid #E7E7E7;
  border-radius: 5px;
  float: left;
  clear:both;
  &.sent{
	background-color: #E4F3DB;
	border: 1px solid #B7D0A7;
	float: right;
  }
  p{
	margin:0;
  }
}

Now, let’s create a form on the right side for sending messages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<:Body>
  <h1>Home</h1>
  {{ if Volt.user }}
	<div class="row">
  	<div class="col-md-4">
    	{{ _users.each do |user| }}
      	{{ if user._id != Volt.user._id }}
        	<div class="contact {{ if params._user_id == user._id }} active {{ end }}" e-click="select_conversation(user)">
          	{{user._name}}
	        </div>
      	{{ end }}
    	{{ end }}
  	</div>
  	{{ if params._user_id }}
    	<div class="col-md-8 well conversation">
      	{{ current_conversation.each do |message| }}
        	<div class="message {{ if message._sender_id == Volt.user._id }} sent {{ end }}">
          	<p>{{ message._text }}</p>
        	</div>
      	{{ end }}
 
      	{{ if current_conversation.count == 0 }}
        	<p>You have no messages yet. Start chatting!</p>
      	{{ else }}
        	<div class="clearfix"></div>
      	{{ end }}
 
      	<form e-submit="send_message" role="form">
        	<div class="form-group">
          	<input class="form-control" type="text" placeholder="Write a message" value="{{ page._new_message }}" />
          	<button type="submit" class="btn btn-primary pull-right">Submit</button>
        	</div>
 
      	</form>
    	</div>
  	{{ end }}
	</div>
  {{ else }}
	<p>This is a sample application built with Volt to demonstrate its real-time capabilities. Please log in to access it.</p>
  {{ end }}

This code first checks if a user is selected before displaying the form. If a user is selected, it retrieves all messages from the current conversation (the conversation with the selected user) using a controller method we’ll define shortly. At the bottom, a form is displayed for sending new messages.

The input value is an attribute we’re defining within the page collection model, as we don’t want this data stored in the data store. Let’s define the ‘current_conversation’ and ‘send_message’ methods in the controller:

1
2
3
4
5
6
7
8
9
def send_message
  unless page._new_message.strip.empty?
	_messages << { sender_id: Volt.user._id, receiver_id: params._user_id, text: page._new_message }
	page._new_message = ''
  end
end
def current_conversation
  _messages.find({ "$or" => [{ sender_id: Volt.user._id, receiver_id: params._user_id }, { sender_id: params._user_id, receiver_id: Volt.user._id }] })
end

The ‘send_message’ method adds a new message to the collection if the message isn’t blank (we’re checking inline for simplicity). Then, it clears the input field by setting page._new_message to an empty string. It’s recommended to add this line to the end of the select_conversation method as well.

The ‘current_conversation’ method queries the _messages collection for messages exchanged between the selected user and the current user.

Wrap Up With Real-Time Notifications

To complete our application, let’s implement a notification system to notify users when they receive new messages.

We’ll introduce a new collection called _notifications and create a new notification after each message is sent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def send_message
  unless page._new_message.strip.empty?
	_messages << { sender_id: Volt.user._id, receiver_id: params._user_id, text: page._new_message }
	_notifications << { sender_id: Volt.user._id, receiver_id: params._user_id }
	page._new_message = ''
  end
end
def select_conversation(user)
  params._user_id = user._id
  unread_notifications_from(user).then do |results|
	results.each do |notification|
  	_notifications.delete(notification)
	end
  end
  page._new_message = ''
end
def unread_notifications_from(user)
  _notifications.find({ sender_id: user._id, receiver_id: Volt.user._id })
end

We also need to clear notifications after a user selects a conversation and views new messages, so we’ve added that logic to the select_conversation method.

Let’s add a notification counter next to the user’s name:

1
2
3
4
5
6
7
8
<div class="contact {{ if params._user_id == user._id }} active {{ end }}" e-click="select_conversation(user)">
  {{user._name}}
  {{ if unread_notifications_from(user).count > 0 }}
	<span class="badge">
  	{{ unread_notifications_from(user).count }}
	</span>
  {{ end }}
</div>

Our application is now complete. You can open multiple browser windows and test Volt’s real-time capabilities.

Volt Is Definitely Worth A Try

While Volt might not yet be as mature or robust as other established Ruby frameworks, it’s a framework worth exploring and learning.

I encourage you to experiment with Volt using the steps outlined in this tutorial. Keep an eye on its development as Volt shows great promise, even in its current beta stage.

With many new features in development, Volt has the potential to become a favorite among developers for their next Ruby project, thanks to its innovative features and ease of use.

Licensed under CC BY-NC-SA 4.0