If you're building web applications with Ruby then you're probably using Rails. Hanami is a young competitor focused on providing a full featured, modern web framework for Ruby developers that is fast, secure and flexible.
Hanami is a new web application framework for the Ruby community. It has been under development since 2014, initially under the name Lotus. Version 1 was released in April 2017 and version 1.1 was just recently released in October.
As the introduction to the Hanami guide says, "If you've ever felt you're stretching against the 'Rails way', you'll appreciate Hanami." While this article isn't a comparison of Hanami and Rails, as we build with Hanami you will see the ways in which they differ and be able to decide which approach you prefer.
Let's investigate building a web application with Hanami with a tried and tested Twilio feature, receiving and responding to text messages.
To build this application you will need a few things:
- A Twilio account (you can sign up for a free account if you don’t have one)
- A Twilio phone number that can send and receive SMS messages
- Ruby (I'm using the latest version for this post, 2.4.2, Hanami requires at least 2.3.0)
- ngrok, so we can expose our development server to Twilio’s webhooks
Once you have those bits we can install Hanami and get started with our application.
To create a Hanami app we first need to install the gem. Open up a terminal and enter:
gem install hanami
Now generate a new Hanami project and install the dependencies:
hanami new messages cd messages bundle install
We have a Hanami app which we can run. Enter:
bundle exec hanami server
Visit http://localhost:2300 and you will see that you are now running your Hanami application.
Hanami projects are described as "monolith first". Microservices might not be necessary when you first start out building a web application, but that may be something you want to take advantage of later. Hanami is built to accommodate that in its architecture.
Hanami projects consist of two parts:
- The core, which includes your models, storage, mailers and other objects that implement the business logic. You can find the core in the
libfolder of your Hanami project
- A collection of apps that are responsible for exposing the functionality to the outside world. You can find the default "web" app in the
Apps are Hanami's solution for sharing core business logic across different ways of presenting and accessing the data. The default app is for the public website for your project, you might create a new app for presenting the same data only to your site's admins.
We'll use a new app for the endpoints we want to expose to Twilio's webhooks as they are a fundamentally different representation to our default web UI. Generate a new app with the following command:
bundle exec hanami generate app webhooks
You will now find a new directory in the
apps directory called "webhooks".
Now we have a separate app for responding to webhooks and we can configure it differently from the web interface. Twilio webhooks expect TwiML in response, so we should always be responding with XML. We can configure our webhooks app to do just that.
apps/webhooks/application.rb and find the
default_response_format. Uncomment it and change it from
:xml and save.
That simple change shows the power of having multiple apps that provide different delivery mechanisms for the core project. The web app still responds with HTML by default and now our webhooks app will default to XML.
To receive and respond to an incoming SMS message we will need an action. Hanami provides generators to write the boilerplate code for us, so run the following:
bundle exec hanami generate action webhooks sms#create
This creates a number of things:
- A route in
apps/webhooks/config/routes.rb. Like in Rails, Hanami tries to map between HTTP verbs and REST when generating routes, so since we generated a "create" action we now have a
- An SMS controller, which is a namespace in which the action classes reside
- A create action, which is a class within the SMS controller that has a
callmethod that will be called when a POST request is made to the route
- A view for the create action, which handles rendering the response to the action
- A template for the create action
It's about time to write some code, not just run generators. Let's get started with a test!
Hanami is very focused on testing, even the getting started guide drives the features you build through tests. In that spirit, let's put a test together for our TwiML endpoint.
Let's build a integration test for now, you'll see why as we continue with our feature.
Hanami includes Capybara for integration tests on interactive pages, but the documentation recommends using
Rack::Test to test machine readable endpoints like API responses. Create a folder in
requests, then within
requests create a file called
sms_spec.rb. Add the following test:
require 'spec_helper' describe "Webhooks SMS response" do include Rack::Test::Methods def app Hanami.app end it "is successful" do post "/webhooks/sms" last_response.must_be :ok? twiml = Twilio::TwiML::MessagingResponse.new.message(body: 'Hello from Hanami!') last_response.body.must_equal twiml.to_s last_response.headers['Content-Type'].must_equal 'application/xml; charset=utf-8' end end
Before we run this test, you will notice we need the twilio-ruby gem to generate our test XML. Install the gem by adding it to your
source 'https://rubygems.org' gem 'rake' gem 'hanami', '~> 1.1' gem 'hanami-model', '~> 1.1' gem 'sqlite3' gem 'twilio-ruby' group :development do
Now, running the test should give us an error.
bundle exec rake spec
Let's fix that.
The action generator created
apps/webhooks/templates/sms/create.html.erb. We already updated our application to return XML, so first rename this file to
create.xml.erb. We can fill this with the TwiML we need to respond to an incoming SMS message:
<?xml version="1.0" encoding="UTF-8"?> <Response> <Message>Hello from Hanami!</Message> </Response>
Run the test again.
Hmmm… it should pass, but it is failing due to the line breaks and indentation in our response. We could fix it by removing this unnecessary whitespace from our template, but let's investigate other ways to produce this output instead.
Unlike Rails, Hanami views are objects. This helps separate concerns as the view objects are solely responsible for returning a string that will be rendered as the body of the response. Because of this we can bypass templates entirely, rendering the body straight from the view.
apps/webhooks/views/sms/create.rb and add the following:
module Webhooks::Views::Sms class Create include Webhooks::View def render twiml = Twilio::TwiML::MessagingResponse.new twiml.message body: 'Hello from Hanami!' raw twiml.to_s end end end
By overriding the
render method we short circuit the template and render TwiML from the view. We use the
raw method to ensure the XML isn't escaped.
This does feel wrong though. The view's job is to render, not create the objects that we are going to render. That is the job of the action.
apps/webhooks/controllers/sms/create.rb, you should find an empty
call method. This method will be called when the action is invoked. We can generate the TwiML here instead, leaving the view to just render the output.
In the action add the following:
module Webhooks::Controllers::Sms class Create include Webhooks::Action expose :twiml def call(params) @twiml = Twilio::TwiML::MessagingResponse.new @twiml.message body: 'Hello from Hanami!' end end end
This code means that when the action is invoked it will generate the TwiML object and then expose it to the view using the
expose class method. Now the view only has to worry about rendering that content. In
apps/webhooks/views/sms/create.rb replace the render method with:
def render raw twiml.to_s end
Run the tests this time and they pass.
At this stage the view isn't really doing very much, so why not just bypass it too? This can lead to a small performance improvement in the application as we avoid creating unnecessary objects.
To bypass the view you can assign to the action's
body. Replace the code in
module Webhooks::Controllers::Sms class Create include Webhooks::Action def call(params) @twiml = Twilio::TwiML::MessagingResponse.new @twiml.message body: 'Hello from Hanami!' self.body = @twiml.to_s end end end
You can now delete the view, along with its test (
spec/webhooks/views/sms/create_spec.rb), and the template since we no longer use them. Run the tests again and you'll see them passing. This means we are ready to connect the app to a phone number.
We need to expose our application to the internet to respond to an incoming text message with the action we just created. First, make sure you're running the application. If you're not, run:
bundle exec hanami server
I like to use a tool called ngrok to expose the application. Follow the instructions to download and install ngrok. Then run ngrok passing the port number we want to forward traffic to. Hanami runs on port 2300 by default, so run:
ngrok http 2300
You will see ngrok running and see the URL that is now forwarding to your Hanami application. Grab the URL, you're going to need that in your Twilio console.
Once you have your number it's time to set the webhook URL for incoming messages. Take the ngrok URL you got earlier and add the path to our action,
/webhooks/sms, to it. Enter this in the field for when a message comes in.
Save that and grab your phone. Send a text message to your Twilio number and wait for your message back from your Hanami application.
We've seen how to set up Hanami to receive and respond to incoming SMS messages. If you want to see the final code check out the application on GitHub. If you want to learn more about Hanami I recommend the Hanami Guides.
Not only have we built a Hanami app that can respond to SMS messages, we've also seen how flexible Hanami is as a framework.
- You can create different apps within one project with different configurations
- There are specific objects for each stage of responding to an incoming request making it easy to separate the concerns of the application
- You can bypass templates and views if you don't need them
This power and flexibility combined makes Hanami a valid competitor to Rails for me. Let me know what you think about Hanami and if you would consider it for your next application. Hit me up on Twitter as @philnash, drop me an email at email@example.com or leave a comment below.
How to receive and respond to text messages in Ruby with Hanami and Twilio was originally published on the Twilio blog on November 21, 2017.