Slack on Rails
Lawson Kurtz, Former Senior Developer
Article Category:
Posted on
Back in the days of Campfire, Hubot was an essential companion to the company chatroom. Hubot was great for the automation of some simple tasks, but the performance of more complex tasks involving external services often seemed more complicated than necessary. And the performance of private tasks was completely impossible. Campfire simply doesn't allow for a better solution.
Now it's 2015 and Slack fever is in full tilt. Slack offers numerous advantages over Campfire, but without question, the most powerful is a greatly enhanced ability to use the chatroom as an interface to other services through its powerful integrations.
Writing custom service integrations for your chatroom is easier than ever before. At Viget, we use Slack as an interface to a few of our internal apps. In this post you'll discover exactly how easy it is to build these integrations into an existing Ruby on Rails app.
Creating a Rails Slack Service
Choosing Your Integration Methods
Slack offers a number of ways to interact with your company's chat data.
For the purposes of a simple command-and-response service (an app that listens to your coworkers' commands, performs some action, and replies with handy info), Slash Commands and Outgoing WebHooks are perfect.
Let's take a closer look at each of these types of integrations.
Outgoing WebHooks
Outgoing WebHooks allow your app to receive Slack messages, and respond publicly to the channel (the responses are visible to all users in the channel).
Example Outgoing WebHook Interaction
Slack Configuration
Choose to receive either A) messages beginning with a trigger word from any channel, or B) all messages from a particular channel.
Receiving
Slack will make a POST request with a payload similar to the following:
{
token<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>a1b23c4d5e6f7g<span class="pl-pds">"</span></span>
team_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>T1234<span class="pl-pds">"</span></span>
channel_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>C1234567890<span class="pl-pds">"</span></span>
channel_name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>dev<span class="pl-pds">"</span></span>
timestamp<span class="pl-k">:</span> <span class="pl-c1">1355517523.000005</span>
user_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>U1234567890<span class="pl-pds">"</span></span>
user_name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>ltk<span class="pl-pds">"</span></span>
text<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>graba who's on first?<span class="pl-pds">"</span></span>
trigger_word<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>graba<span class="pl-pds">"</span></span>
}
Responding
Respond as JSON, with your message at the key text
.
{
<span class="pl-s"><span class="pl-pds">"</span>text<span class="pl-pds">"</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>This is my response.<span class="pl-pds">"</span></span>
}
Slash Commands
Slash Commands allow your service to receive command messages (invisible to other users) and respond privately to the user issuing the command.
Example Slash Command Interaction
Slack Configuration
Receiving
Slack will make a POST request with a payload similar to the following:
{
token<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>a1b23c4d5e6f7g<span class="pl-pds">"</span></span>
team_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>T1234<span class="pl-pds">"</span></span>
channel_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>C1234567890<span class="pl-pds">"</span></span>
channel_name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>dev<span class="pl-pds">"</span></span>
user_id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>U1234567890<span class="pl-pds">"</span></span>
user_name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>ltk<span class="pl-pds">"</span></span>
command<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>/graba<span class="pl-pds">"</span></span>
text<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>book me something slick<span class="pl-pds">"</span></span>
}
Responding
Text response will be relayed to the user as the command response. (No JSON.)
Updating Your Rails App for Slack
Once the Slack configuration is complete, the easy part begins. Adding Slack integration endpoints to an existing Rails app is a trivial affair.
config/initializers/slack.rb
First we'll add our Slack integration tokens to our app so that we'll be able to authenticate requests made to our service's Slack endpoint.
You could also put this in config/secrets.yml
if you'd prefer. It's your world; I'm just blogging in it.
We're reading the tokens from ENV
, so be sure to actually set that environment variable (e.g. SLACK_TOKENS=a1b23c4d5e6f7g,a9b8c7d6e5f4g3
).
<span class="pl-k">module</span> <span class="pl-en">Slack</span>
<span class="pl-c1">TOKENS</span> <span class="pl-k">=</span> <span class="pl-c1">ENV</span>.fetch(<span class="pl-s"><span class="pl-pds">'</span>SLACK_TOKENS<span class="pl-pds">'</span></span>).split(<span class="pl-s"><span class="pl-pds">'</span>,<span class="pl-pds">'</span></span>)
<span class="pl-k">end</span>
config/routes.rb
resources <span class="pl-c1">:slack_responses</span>, <span class="pl-c1">only:</span> <span class="pl-c1">:create</span>
app/controllers/slack_responses_controller.rb
Now Slack message and command POSTs are flowing into our SlackResponsesController
. In this controller we only need to account for the required response difference between Slash Commands and normal Slack messages. (We can do this by checking for a :command
parameter in the POST. We delegate all responsibility for creating the response to a Responder
.
<span class="pl-k">class</span> <span class="pl-en">SlackResponsesController<span class="pl-e"> < ApplicationController</span></span>
skip_before_filter <span class="pl-c1">:verify_authenticity_token</span>
before_filter <span class="pl-c1">:verify_slack_token</span>
<span class="pl-k">def</span> <span class="pl-en">create</span>
render <span class="pl-c1">nothing:</span> <span class="pl-c1">true</span>, <span class="pl-c1">status:</span> <span class="pl-c1">:ok</span> <span class="pl-k">and</span> <span class="pl-k">return</span> <span class="pl-k">unless</span> responder.respond?
<span class="pl-c"># Respond differently to Slash Command vs Webhook POSTs</span>
<span class="pl-c"># See `Responding` sections above for the require difference.</span>
<span class="pl-k">if</span> params[<span class="pl-c1">:command</span>].present?
render <span class="pl-c1">text:</span> responder.response.to_s
<span class="pl-k">else</span>
render <span class="pl-c1">json:</span> { <span class="pl-c1">text:</span> responder.response.to_s }
<span class="pl-k">end</span>
<span class="pl-k">end</span>
<span class="pl-k">private</span>
<span class="pl-k">def</span> <span class="pl-en">responder</span>
<span class="pl-smi">@responder</span> <span class="pl-k">||=</span> <span class="pl-c1">Slack</span>::<span class="pl-c1">Responder</span>.<span class="pl-k">new</span>(params[<span class="pl-c1">:text</span>])
<span class="pl-k">end</span>
<span class="pl-k">def</span> <span class="pl-en">verify_slack_token</span>
render <span class="pl-c1">nothing:</span> <span class="pl-c1">true</span>, <span class="pl-c1">status:</span> <span class="pl-c1">:forbidden</span> <span class="pl-k">and</span> <span class="pl-k">return</span> <span class="pl-k">unless</span> <span class="pl-c1">Slack</span>::<span class="pl-c1">TOKENS</span>.include?(params[<span class="pl-c1">:token</span>])
<span class="pl-k">end</span>
<span class="pl-k">end</span>
lib/slack/responder.rb
This is where the magic happens. It's up to you to turn the Slack message into a useful reply within #response
. (If your service needs more information than the message, simply pass the additional info into the responder object during instantiation.)
<span class="pl-k">class</span> <span class="pl-en">Slack::Responder</span>
<span class="pl-k">def</span> <span class="pl-en">initialize</span>(<span class="pl-smi">message</span>)
<span class="pl-smi">@message</span> <span class="pl-k">=</span> message
<span class="pl-k">end</span>
<span class="pl-k">def</span> <span class="pl-en">respond?</span>
response.present?
<span class="pl-k">end</span>
<span class="pl-k">def</span> <span class="pl-en">response</span>
<span class="pl-smi">@response</span> <span class="pl-k">||=</span> <span class="pl-s"><span class="pl-pds">"</span>You asked: <span class="pl-pse">#{</span><span class="pl-s1">message</span><span class="pl-pse"><span class="pl-s1">}</span></span><span class="pl-pds">"</span></span>
<span class="pl-k">end</span>
<span class="pl-k">private</span>
<span class="pl-k">attr_reader</span> <span class="pl-c1">:message</span>
<span class="pl-k">end</span>
This is what our responder looks like for Viget's conference room management service Graba.
That's it! Adding Slack integrations to your existing Rails services is straightforward and makes everyone's life easier. So what are you waiting for? Happy Slacking.