As the web evolves and users become more demanding, the need for web applications to be interactive and immediate grows. The engineering team at ClearVoice has been working hard to add interactive features to the ClearVoice platform to take advantage of the latest browser technology. Last month we released a major update that includes realtime messaging, presence and collaborative editing.
Before starting development of the realtime features we explored several options. We looked at building a home-grown solution with socket.io, we tested hosted services such as Pusher and PubNub, and finally we evaluated the beta of ActionCable — the new realtime system built into Rails 5. At the time, ActionCable had no clear release date. Ultimately we ended up going with Pusher to solve our needs. This decision came down to the ease of implementation, cost, reliability and popularity in the community. We were not able to select ActionCable not only because it was still in beta, but also due to the lack of fallback support in the case of failed WebSocket connections. Both Pusher and PubNub have several layers of graceful fallbacks in their client implementations. They both provide fallbacks if the browser does not have native WebSocket support and will also fallback to HTTP longpolling in the case that a firewall or other network device prevents WebSockets from functioning correctly.
Although we did not select it at the time, I feel that ActionCable has a lot of potential. It makes building realtime apps with Rails incredibly straightforward. I recently gave a talk at the local Ruby::AZ Meetup group about ActionCable, including live-coding a chat system with user registrations and authentication, to take your content operations to the next level.
Here’s a quick peek at getting a very basic ActionCable example wired up:
First off, we need to make sure we have Ruby 2.2.2+ installed. I’m not going to include ruby install instructions here, but I recommend using rbenv, which can be easily installed via brew on Mac OSX.
As a prerequisite, check to ensure you’ve got Ruby 2.2.2+ and install rails 5.0.0rc1:
$ ruby -v ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-linux] $ gem install rails --pre # --pre = install prereleases Successfully installed rails-5.0.0.rc1
1. Create our app (the _5.0.0rc1_ tells the rails shim that we wish to use rails 5.0.0rc1), initialize a git repository and make our first commit just in case we need to revert anything we do:
$ rails _5.0.0rc1_ new ac create create README.md create Rakefile create config.ru create .gitignore create Gemfile create app ... $ cd ac $ git init && git add . Initialized empty Git repository in /home/jnappi/src/ac/.git/ $ git commit -am "initial commit" [master (root-commit) 9b33e47] initial commit 80 files changed, 1147 insertions(+) ...
2. Create a rooms controller, a message model, run the migrations to create our messages database table, commit and create a test message:
$ rails generate controller rooms show create app/controllers/rooms_controller.rb route get 'rooms/show' ... $ rails generate model message content:text invoke active_record create db/migrate/20160508222622_create_messages.rb ... $ rails db:migrate == 20160508222622 CreateMessages: migrating =================================== ... $ git add . $ git commit -am "Rooms Controller and Message Model" [master 10c5348] Rooms Controller and Message Model 12 files changed, 72 insertions(+) create mode 100644 app/assets/javascripts/rooms.coffee ... $ rails c irb(main):001:0> Message.create content: 'hello world' (0.1ms) begin transaction SQL (0.4ms) INSERT INTO "messages" ("content", "created_at", "updated_at") VALUES (?, ?, ?) [["content", "hello world"], ["created_at", 2016-05-08 22:28:43 UTC], ["updated_at", 2016-05-08 22:28:43 UTC]] (7.1ms) commit transaction
3. Okay, we’ve got the basic plumbing for a rails app with a rooms controller and a message model. Lets point the root route of the app to rooms#show. Open up config/routes.rb and replace “get ‘rooms/show'” with “root to:’rooms#show’:
Rails.application.routes.draw do root to:'rooms#show' end
4. Now it is time to fire up the app and see what we have:
$ rails s => Booting Puma => Rails 5.0.0.rc1 application starting in development on http://localhost:3000 => Run `rails server -h` for more startup options Puma starting in single mode... ...
Open up in the browser at http://localhost:3000 and:
5. Success! Our controller is loading. Now let’s display our messages in the room – populate the following files:
rooms_controller.rb: def show @messages = Message.all end app/views/rooms/show.html.erb:
ActionCable Chat
app/views/messages/_message.html.erb:
<%= message.content %>
Lets commit these changes: $ git add . $ git commit -am “Add rooms/message views”
Now open up http://localhost:3000 again and see what we’ve got:
What we have here is a basic rails app with our test message rendered. This is Rails 101. Just about the simplest possible app. With ActionCable it’s just as simple to add realtime functionality, so lets do it:
6. Generate a ActionCable channel with a ‘speak’ action:
$ rails generate channel room speak create app/channels/room_channel.rb ...
7. Implement the received and speak in app/assets/javascripts/channels/room.coffee:
App.room = App.cable.subscriptions.create "RoomChannel", connected: -> # Called when the subscription is ready for use on the server disconnected: -> # Called when the subscription has been terminated by the server received: (data) -> # Called when there's incoming data on the websocket for this channel $('#messages').append "
” speak: (message) -> @perform ‘speak’, message: message
8. Update the channel subscription and implement ‘speak’ in app/channels/room_channel.rb:
class RoomChannel < ApplicationCable::Channel def subscribed stream_from "room_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end def speak(data) ActionCable.server.broadcast 'room_channel', data['message'] end end
And lets commit:
$ git add . $ git commit -am "ActionCable Enabled"
Restart the rails server process and test in two windows at http://localhost:3000:
To see a complete chat app built including user registrations and authentication check out the video I recently made after giving a talk on the subject: