Four days with Action Cable

This week has been final project week at Craft Academy. This time around the students had hard time deciding what to do for their last two weeks so I came up with an idea: “Let’s build a Pub Quiz platform for managing and delivering… you know…, quizzes. ;-) And let’s use Push Notifications just for the fun of it.”

The Quizmaster was born!

This is what we went through.

Monday at around 4 pm

Monday was a productive day. The project was put through a mini design sprint. 5–6 hours of splitting it up in epics, features and chores, formulating user stories, drawing lo-fi’s, etc. Discussing everything from multiple angels and agreeing on a definition of ‘done’ for each feature. Voting on complexity. More discussions. More edits… You know the drill: Just Enough Upfront Design but the backlog needs to be filled with stuff the team can work on.

In a ‘real’ project the design sprint is usually much longer but remember that this is a learning project with a rather short time frame — the MVP needs to be deployed in 10 to 14 working days.

Tuesday 9.45 am

Yeah, so Action Cable is this cool way of using websockets within a Rails application and use push notifications. One of the features of the Quizmaster app is to push out questions to every team of players at once and allow them to submit answers. Hell yeah!

We started with a feature about allowing the team to access the Quiz page.

As a Quizmaster
 in order to give out my Quiz
 I need to generate a unique code and a page where Teams can access the Quiz.

The idea was to generate a random 4 character code that can be used to identify the Quiz and build a page where a Team can input that code in order to access the Quiz and play the game. We were very keen on using notifications so we wanted to push out a message to that page saying something like ”Welcome to quiz: Trivia” when it was being rendered.

That proved to be a mistake.

Tuesday 16.30 pm

After a loooong mob programming session dissecting the Action Cable setup and configuration and a lot of Googling for examples, we ended up learning the following:

  • Most of the examples and tutorials an Action Cable are about building a chat (I guess the usual blog application example fell out of fashion). Except one showing how to build a chess game.
  • Most of the examples and tutorials an Action Cable seem to be copies of each other.
  • There seems to be a stalemate in the work of the Rails and RSpec teams regarding AC testing (there is a PR for AC TestCase and the RSpec team is waiting for it to be merged before moving on…)
  • With no straight forward way to unit test Action Cable, integration tests looks like a way to go. Perfectly fine for us, since in our workflow, we value our acceptance tests a lot.

Tuesday proved to be an unproductive day in terms of shippable code but we did learn a lot. At the end we had to abandon the idea of pushing out a welcome message to the team on accessing the Quiz page and ended up with this pull request (we decided to keep the code we create while setting up Action Cable, even if it ended up not being used in this PR. A bit of feature creep I know…).

Wednesday 5.30 am

I woke up with a man cold but also with a realisation that we needed to make sure that we can open up multiple browser windows in our tests and fire up the Redis server in test environment. Without that we would not be able to test if the push notifications are making their way to our Quiz page.

I sat down and fired up my IDE. I reckoned I had at least an hour and a half before I had to wake my family up and thought I could make a simple scenario for viewing multiple windows in Cucumber. I was mistaken.

I started out with the following scenario:

Feature: As a Team
in order to play Quizmaster
I need to be able to access the Quiz interface.

there is a quiz called "Trivia"
And there is a quiz called "Trivia 2"

Scenario: Accessing two quizzes in separate windows
Given I am on the quiz page for "Trivia"
And I switch to a new window
And I am on the quiz page for "Trivia 2"
Then I should see "Trivia 2"
And I switch to window "1"
Then I should see "Trivia"
Then I should have "2" active windows

Making it pass was not as straight forward as I thought, but at the end I prevailed — despite the man cold.

I learned about the Window object in Capybara and after some research I was able to come up with the following step definitions and the scenario went green.

Given(/^there is a quiz called "([^"]*)"$/) do |name|
FactoryGirl.create(:quiz, name: name)

Given(/^I am on the quiz page for "([^"]*)"$/) do |quiz_name|
quiz = Quiz.find_by(name: quiz_name)
visit quizmaster_quiz_path(quiz)

Given(/^I switch to a new window$/) do
window = open_new_window

Given(/^I switch to window "([^"]*)"$/) do |index|
switch_to_window(windows[index.to_i - 1])

Then(/^I should have "([^"]*)" active windows$/) do |count|
expect(windows.count).to eq count.to_i

The time was around noonish.

Wednesday — around bedtime

The rest of the day was spent on getting push notifications visible in test environment. While the students worked on other features dealing with some UI aspects of the app, I was fighting with getting this crucial part of our tests to work.

Feature: As a Quizmaster
in order to show individual Questions
I need to be able to push Questions one by one to the Question page.

there is a quiz called "Trivia"
And the following questions exist in "Trivia":
| body | answer |
| What is 2+2? | Four |
| Who is awesome? | Thomas is ok |
there is a team named "Craft Academy"
And I am on the quizmaster page for "Trivia"

Scenario: I send the first question
Given there is a "team_id" cookie set to "Craft Academy"
When I am on the quiz page for "Trivia"
And I switch to a new window
And I am on the quizmaster page for "Trivia"
When I press the "Send" button for question "1"
And I switch to window "1"
Then I should see "What is 2+2?"

My main objective was to:

  • configure Action Cable for test environment
  • start a Redis server when running tests
  • make sure a cookie was set in the browser to identify the team currently playing the game

It was not totally unproblematic but by joining forces with Amber and Raoul we got a setup in place.

The cookies step was inspired by a solution I was lucky enough to find on Stack Overflow.

Given /^there is a "([^\"]+)" cookie set to "([^\"]+)"$/ do |key, value|
team_id = Team.find_by(name: value).id
case Capybara.current_session.driver
when Capybara::Poltergeist::Driver
page.driver.set_cookie(key, team_id)
when Capybara::RackTest::Driver
headers = {}
Rack::Utils.set_cookie_header!(headers, key, team_id)
cookie_string = headers['Set-Cookie']
raise "no cookie-setter implemented for driver #{}"

For running the Redis server we added gem 'redis_test' to the :development, :test group in the projects Gemfile and added an `@action_cable` before hook in our /features/support/hooks.rb.

Before '@action_cable' do

After '@action_cable' do

at_exit do

The Redis server was firing up — we knew that for a fact by checking the logs. Promising.

Yet the notifications did not show up.

What followed was a frustrating process of debugging the code. We all worked on trying to figure out what the hell was missing in our setup and I was getting a bit desperate.

Without this the entire project was doomed to fail. Remember that everything we do during the bootcamp is supposed to follow the Acceptance-Unit Test Cycle. Our primary objectives are to learn, follow best practices and deliver code that is thoroughly tested. If we couldn’t get a working solution in place we might have been forced to abandon this project .

Wednesday ended without a solution in place. We ware close but it didn’t matter as that last step was eluding us.

Thursday — 6.00 am to 9.00 am

Sleep is good. Really good.

My man cold was still holding me in a firm grip but despite my near-death condition I again woke up with an Eureka moment.

In retrospective, it is quite obvious, but it’s easy to forget that the application server used to run tests is not the same as the one used in production or even development environments. Most of the time it does not matter and it is perfectly fine to use Webrick, the default server. Not in this case. Action Cable relies on a threaded server so what we ware missing in our test setup was to use the Puma server.

At the time this was merely a theory of mine but I was eager to get started. I promptly made sure my features/support/env.rb file included the necessary setting to run Puma.

Capybara.register_server :puma do |app, port, host|
require 'puma' do |s|
s.add_tcp_listener host, port

Capybara.server = :puma

And ran my scenarios.

We all know the feeling when test are going green after a long struggle. I intentionally left the terminal outputs as a reminder of the many debugging checks we had in the code. They have since been removed.

It was a great feeling to post to our Slack channel that I managed to get those scenarios to pass. I had still some time to do some code cleaning and update Amber Wilkie’s pull request with my code.

Another source of concern was how our Continuous Integration service would deal with all these settings. Much to our surprise that part was actually totally cool. Travis-CI just… worked!

Lessons learned

In order to be able to test Action Cable we need to:

  • Run a threaded server (Puma)
  • Run a Redis server
  • Use a javascript enabled driver (Poltergeist)
  • Use the window object to access multiple browser windows

In the grand scale of things it might not have been a big deal but I feel really proud that we got this to work. I was yet again reminded why I love my job and what I do so much — solving problems like this and enabling my students to build cool applications is what drives me.

Finally, my kudos to Amber Wilkie, Raoul DIFFOUO, Susanna Larsdotter and Viktoria Blok from Craft Academy for working with me on this small but challenging project, Patrick Bolger, Brian Giaraffa and tansaku from Agile Ventures for all good pointers and support and, of course, DHH for the great Rails framework and for making Action Cable available to us.

At the time this post is being published, the project team has another week of work ahead of them and I’m sure they will face many challenges but I’m also sure they will come up with a great application. Hopefully the work we did with setting the stage for a way to write acceptance tests will help them reach their goals.

Perhaps our experience will help you as well.

If you are interested to have a closer look at the code feel free to visit the projects Git Hub repository.