Stac Blog

May 15th, 2015 by Josh

Building a Slack slash command with Sinatra, Finch and Heroku

Slack is awesome. We use it on a daily basis to keep in touch with pretty much everyone. With all the positive press it's been having, we thought we'd see if we could create a simple bot which would respond to slash commands.

Overview

For the purpose of this post, we're going to write a bot which responds to questions about our Hey! events. Ideally, it will respond to the following sub-commands:

  • /hey when - When is the next event
  • /hey what - Who is speaking at the next event, and where can I find out more information on each talk
  • /hey unsupportedcommand - Respond to missing commands with a friendly error message

Tools

To achieve this we're going to use a few free tools:

  • Sinatra - A Ruby DSL for creating web applications
  • Finch - A great tool for exposing local sites to the Internet
  • Heroku - Cloud hosting platform, deployed to via Git

You can find the Sinatra code for this post on GitHub.

Steps

Firstly we're going to write a simple Sinatra application to respond to Slack requests. At its most basic, it will receive a question from Slack which it needs to then return a plain text response.

We'll create a Gemfile to tell Ruby about our dependencies:

source 'https://rubygems.org'

gem 'rack'
gem 'sinatra'
gem 'foreman'

We've also added Foreman here which we'll want to use when we deploy the application to Heroku.

Next up we'll write a basic version of our application, we'll put the contents into a file called config.ru (a Rack config file).

require 'sinatra'

post '/' do
  text = params.fetch('text').strip

  case text
  when 'when'
    'TODO'
  when 'what'
    'TODO'
  end
end

run Sinatra::Application

Right now, this takes the incoming text parameter (the command typed by the user that Slack sends), and responds with some placeholder text depending on the sub-command.

Now let's see if we can get Slack talking to this application. Run the application with the following command from the project directory:

bundle exec rackup config.ru

Assuming everything has worked correctly, you should see something like:

[2015-05-14 17:41:14] INFO  WEBrick 1.3.1
[2015-05-14 17:41:14] INFO  ruby 2.1.1 (2014-02-24) [x86_64-darwin14.0]
[2015-05-14 17:41:14] INFO  WEBrick::HTTPServer#start: pid=57001 port=9292

This is where Finch comes in. We need to expose this application to the Internet so Slack can talk to it.

Once Finch is installed, we'll tell it to forward your local application to an external URL which we can give to Slack:

> finch forward localhost:9292

→ Requesting connection... ✔
→ Establishing secure connection... ✔

The following sites are now being forwarded. Press CTRL+C at any
time to end your session:

--------------------------------------------------------------
| Public URL                         | Private URL           |
--------------------------------------------------------------
| https://cream-uneven.usefinch.io   | http://localhost:9292 |
--------------------------------------------------------------

Great, we're all set to configure Slack. Under your own Slack account, visit /services/new/slash-commands to add a new command, and configure it as below.

Setting up a Slack integration

When that's all saved, you should be able to test out your slash command integration! Try typing the following into any slack channel:

/hey when

And you should get back TODO. It works! Let's improve our application a little further.

require 'sinatra'

InvalidTokenError = Class.new(Exception)

post '/' do
  raise(InvalidTokenError) unless params[:token] == ENV['SLACK_TOKEN']

  user = params.fetch('user_name')
  text = params.fetch('text').strip

  case text
  when 'when'

    <<-TEXT
The next Hey! event will be held on the 20th May from 7:30pm at The Belgrave in central Leeds.

Hopefully see you then #{user}!

http://hey.wearestac.com/
TEXT

  when 'what'

    <<-TEXT
The next Hey! event has two lectures planned. The first one is with Rich Fiddaman discussing everything hospitality. The second is with Matt Dix discussing Leeds Indie Food Festival.

http://hey.wearestac.com/lectures/a-pint-with-the-pub-landlord

http://hey.wearestac.com/lectures/kickstarting-a-city-wide-food-festival
TEXT

  else

    'Unknown command :cry:'

  end
end

run Sinatra::Application

We've added a few things here:

  • Some real copy for the when and what sub-commands
  • We're raising a custom error if the request isn't coming from Slack. We can verify this by checking the request contains a private token that only you and Slack know about
  • We're also dealing with missing commands by falling back to a friendlier error message

Now when you type /hey when you should see something like this:

Working slash command

And if you mistype a command:

Missing command

Deploying to Heroku

Now we've got our application working and tested, we need to push it to Heroku. First create a Procfile so Heroku knows how to run the application:

echo 'web: rackup config.ru' > Procfile

Then add everything to Git and push it:

git init
git add -A
git commit -m "Initial commit"
heroku create
git push heroku

The heroku create command should return the URL your application is available at. Now your application is hosted! All that's left to do is change the URL in the Slack slash command settings and you're good to go.

Future Improvements

A few potential improvements to this application could be:

  • Remove the hard-coded event information. This would ideally pull from a Hey! API
  • Embed more information about the events in the what command
  • Add another sub-command to subscribe to the Hey! mailing list directly from Slack

We hope you've found this brief tutorial useful, let us know how you're using slash commands in the comments.

November 25th, 2014 by Josh

Log tagging in Rails

Sometimes when debugging issues in a live application, it can be hard to pair up log entries with application requests. The water can become even murkier when you've got many application processes accepting requests from multiple entry points (such as a load balancer).

One way of dealing with this is to tag each request with a unique ID, and use that ID to trace the request throughout its lifespan. Heroku has a great article on HTTP request IDs over here.

We needed to do this recently whilst deploying a large application to the Heroku platform, so we decided to look into log tags within Rails.

It's quite simple to get tags showing up in your Rails logs. Say we want to show the current request ID against each log entry, all we need to do is configure the log_tags setting in an initializer:

# config/initializers/log_tags.rb

My::Application.config.log_tags = [ :uuid ]

Now you can filter the log content by a particular request ID to see all output related to a single request (including any requests to assets if you are using Rails to serve static assets). Some services let you search for keywords in logs (such as PaperTrail) which can make this process easier.

For example, a controller request before tagging might look like this:

Started GET "/dashboard" for 127.0.0.1 at 2014-11-24 15:48:30 +0000

And after:

[a00c54e0-874f-4358-a7b9-accef0c407c6] Started GET "/dashboard" for 127.0.0.1 at 2014-11-24 15:48:30 +0000

This is particularly useful on Heroku, as they set X-Request-ID at the routing layer, meaning we can trace the request from the very start when the request gets accepted by the router (before it hits any of your application dynos).

It's worth noting that if X-Request-ID is not set, Rails will generate a unique ID for the request itself, meaning you can still use this feature.

As well as :uuid there are many other symbols you can use to provide more useful tags in your logs such as :host and :remote_ip. You can also provide a lambda/proc as an item in the settings array should you need to define more custom behaviour.

July 25th, 2014 by Josh

Hey!Stac Relaunched

Hey!Stac

Since we launched Hey!Stac a year ago, we've put on 12 events, had 35 talks and scoffed 704 brownies. Following a successful year we wanted to take stock of what the brand had become and where we wanted to take it in the future.

The end result was a new brand and site which presents the content in a way which allows it to be explored, and a new event format which we're launching in August.

You can read more about our plans for the future over here.

July 11th, 2014 by Josh

Case Study: NHSx

It's not often that you get a call from the NHS asking you to build a learning platform from scratch, using the latest technologies and delivered using agile management practices, but one February morning we were faced with exactly that.

Alongside good friend Harry Roberts (who's also done a nice write up of the project here) we met Jason, the NHS Leadership Academy’s Programme Lead, and after a short discussion we agreed this was definitely a project we'd like to be involved in.

NHSx

Overview

The aim of the project was to build a learning platform that could be used to distribute educational content to anyone, at any level within the NHS. Whether they're a nurse or an executive, the platform needed to provide a place for them to take part in online programmes and report their progress back to coaches and programme leads.

On top of this, we also needed to make sure that the content packages were easy to author and manipulate. The platform needed to satisfy the users who administered the programme content just as much as the participants, as the quality of the content was just as important. There were already systems out there that supported a variant of what we needed, but they didn't fit the delivery model we had in mind, so we worked with the authors to build something more suitable for the platform. This resulted in a custom package format that meant authors could create learning content, add resources such as videos and images, and bundle it all up into a single upload to publish on the platform.

Process

One of the most refreshing aspects to this project was how it was run and delivered. IT projects in the public sector are mostly publicised for being monolithic beasts resulting in bloated software and off-the-mark deliverables, but I'm glad to say that this project couldn't have been more different. It's a huge credit to Jason and his team that the project was delivered on time and to the specification.

On top of that, it was all done in an agile manner. We used tools like Trello and Slack to keep communication tight, and GitHub to keep conversations around code focused and relevant. During a series of 14 sprints we took the project from MVP to final deliverable. We used a staging server to preview code before production releases to ensure internally we were happy with how things worked, and we used automated deployment processes to push code live.

It was made even more enjoyable by Jason allowing the development team to just "get things done" without too much bike-shedding about how we were doing things, with the knowledge that we had the experience to deliver what was asked of us.

Detail

The main platform project was build in Rails 4, using a small handful of gems to speed up development of some common features. Alongside this, Harry worked on a UI Toolkit written in Sass. We worked on the functional and presentational sides of the application in separate branches (using Git), until we were ready to pair the work up.

A bit more detail about the technology used on this project:

  • To run the app itself we used Foreman and Unicorn. Using Unicorn allowed us to deploy the app with zero downtime using rolling restarts
  • Dragonfly was used a lot, not only for image uploads, but storing assets and content packages, and post-processing assets into cloud storage
  • App Signal meant we were notified of any production errors or performance regressions
  • Rack Cache helped us tune some heavy page requests and dragonfly downloads
  • Ember.js allowed us to create mini applications that we could embed into certain parts of the site which required richer features (such as private user portfolios)
  • jBuilder helped us build a more consistent API for the JavaScript applications by treating our JSON views just like any other Rails view
  • Capybara and Site Prism allowed us to thoroughly test the entire stack, along with the usual unit and controller specs

Alongside the technologies listed above we also created some private gems to support some more bespoke features of the platform such as content delivery (which used our custom package format), application permissions, and feature flags so we could easily enable features for certain participants.

Summary

It was a pleasure to be a part of such an agile team, especially on a project with such a large impact on its user base. We can't wait to see how the project evolves and matures as more learning content gets added, and we look forward to seeing the Leadership Academy continue to improve the jobs and lives of people who have used the platform, delivered by a small team in just a few months.

April 15th, 2014 by Josh

Action Mailer Interceptors

There's a little known feature in ActionMailer called Interceptors. If you've ever wanted to conditionally alter an outgoing email within a Rails application, Interceptors are what you're looking for.

Interceptors allow you to do exactly that, intercept an outgoing email. It's at this point you can choose to alter the to address, add a BCC address, or stop the mail from sending altogether.

Example

Let's look at how Interceptors work. Firstly, define a class which responds to the .delivering_email class method. This will accept a single argument, the mail object:

class RedirectOutgoingMails
  class << self

    def delivering_email(mail)
      mail.to = 'test@example.com'
    end

  end
end

In the above Interceptor, we're overriding the to address to only send to test@example.com regardless of who the mail was meant to go to.

Now the Interceptor needs registering with ActionMailer:

ActionMailer::Base.register_interceptor(RedirectOutgoingMails)

Once registered, this will intercept all mails. This might not be what you want, so let's look at adding it only for certain environments:

if %w( development staging ).include?(Rails.env)
  ActionMailer::Base.register_interceptor(RedirectOutgoingMails)
end

Now we'll only have the mails intercepted in our development or staging environments. This can be particularly useful if you use sample data from production on staging, and don't want to accidentally email real users (although we wouldn't recommend directly using live data on staging).

Other Uses

As we have access to the entire mail object, we can do more than just alter the to address. We can, for example, stop delivery of the mail altogether:

class HaltMailDelivery
  class << self

    def delivering_email(mail)
      mail.perform_deliveries = false
    end

  end
end

Or we could BCC in all admin users to all emails:

class BccAdminsToAllMails
  class << self

    def delivering_email(mail)
      mail.bcc = User.where('admin = ?', true).pluck(:email)
    end

  end
end

Refactoring

One slight improvement we could make to the original example is to source the override email addresses from the ENV constant, so we don't need to make code changes when we want to change the to addresses:

class RedirectOutgoingMails
  class << self

    def delivering_email(mail)
      mail.to = to_addresses
    end

    def to_addresses
      raw = ENV['MAIL_INTERCEPT_ADDRESSES'] || ''
      raw.split(',').map(&:strip)
    end

  end
end

Now all we need to do is change the config value in MAIL_INTERCEPT_ADDRESSES and restart our application and the changes will take effect.

January 27th, 2014 by Josh

2013 Recap & Latest News

Better late than never, we wanted to take a moment to recap last year and provide a brief update of plans moving forward. 2013 was a great year for Stac, and we're incredibly excited about what we've got lined up for this year.

Hey!Stac

June saw the first Hey!Stac event take place just below Stac HQ in The Faversham. Since then, we've seen a great community grow with many loyal, familiar faces.

With 17 talks so far, focusing on a wide range of topics from development to mental health, we're really interested to see where we can take the event over the next year.

Hey!Stac's aim has always been to build and support a community of skilled and passionate people, whether they're developers, designers, writers, project managers or business owners.

This year we're focusing on putting more effort into planning the events and supporting speakers to ensure we can keep things flowing. In light of this, we're happy to announce the next three events will be on the 28th January, 25th February and the 25th March.

We're also looking for formal sponsorships to ensure that we can put on the best events possible; if you're a company looking to get involved, please get in touch.

Client Success

New Year's Eve saw the conclusion of a year long project to launch the next iteration of our Manchester client Fatsoma's ticket sales platform. It was great to see them top a new record in sales over the Christmas period, and we're sure that the success of the platform and the business itself will continue to grow as they expand their team. It's been a pleasure working with a company with such a clear vision on how the future of ticketing should be, and we're proud to have played a part in that.

Leeds, Manchester and London

Alongside Leeds and Manchester we're excited to add London as a third location for Stac. With a thriving startup culture and many interesting technical problems to solve we're really keen to meet businesses who we can support and grow with. Although we're taking our offering down to the capital, we're not abandoning our northern roots, and Leeds will remain firmly planted as our base.

Blog Content

We're also looking forward to putting more effort into publishing content surrounding Rails and Ember.js, as well as other non-technical posts. Keep an eye out for more posts coming soon.

Thanks to all our clients and the community that's supported us through 2013, we hope to see many new faces at our upcoming events, and here's to 2014!