Ruby

Use Slack's Incoming Webhooks from your Rails app

Incoming Webhooks are the simplest way to post messages from your application into your users' Slack channels. They use plain HTTP requests with JSON payloads to post text messages and rich content alike.

If you're building a Slack app, with Rails, you probably want to make use of incoming webhooks to send custom message notifications about your app. To do this, we'll authenticate your app to your user's Slack team and extract the incoming webhook URL from the API.

Embed the "Add to Slack" button

If you haven't already registered your app with Slack, go to the Your Apps page and click "Create New App". Give your app a name and click "Create App".

Create an App

After you've created your app, head over to the Slack Button documentation page and scroll down to the "Add the Slack button" section. There you'll find a form where you can customize the code for embedding your Slack button. Be sure to select your app name from the list. Also be sure the "incoming webhook" option is selected.

Add the Slack Button

Paste the resulting code into the view where you want your user to authenticate their Slack team with your application. You'll most likely want this to occur after the user has already authenticated themselves with your app so they'll be able to log back in and change their preferences.

Create a callback endpoint

When your users click the "Add to Slack" button, they'll be taken to a Slack-hosted page where they'll verify that they want to give you the ability to post to Slack on their behalf. After they confirm, Slack will redirect to an OAuth Redirect URL. This URL will receive a special code from Slack that will grant your app access to Slack's API features, including incoming webhooks.

Before we build the endpoint, add the Slack API gem to your Gemfile. I came across two popular gems at the time of this writing. The one we'll use is the slack-api gem:

# Gemfile
gem 'slack-api'

Run bundle install to download the gem and load it into your app.

Next, define a route in your routes.rb file for our new endpoint:

# config/routes.rb
Rails.application.routes.draw do
  # ...
  get '/auth/callback', to: 'slack#callback'
end

Then, create a corresponding controller in app/controllers:

# app/controllers/slack_controller.rb
class SlackController < ApplicationController
  # If you're using Devise to authenticate your
  # users, you'll want to first ensure you
  before_action :authenticate_user!

  def callback
    client = Slack::Client.new
    response = client.oauth_access(
      client_id: <YOUR_SLACK_CLIENT_ID>,
      client_secret: <YOUR_SLACK_CLIENT_SECRET>,
      code: params[:code],
      redirect_uri: "http://localhost:3000/auth/callback"
    )

    if current_user.update_attributes(
      slack_access_token: response['access_token'],
      slack_incoming_webhook_url: response['incoming_webhook']['url']
    )
      redirect_to root_path
    else
      render text: "Oops! There was a problem."
    end
  end
end

First, we create a before_action which authenticates the user before entering the controller action. It's likely you'll want to know who is clicking the "Add to Slack" button so you're able to save their Slack credentials for later use and/or discarding.

Then, in the action, we create a new Slack::Client object and call the Slack API method oauth.access which will grant us access to the Slack access token, incoming webhook URL, and other metadata associated with the Slack account we just authorized.

You'll want to change the client_id and client_secret settings to reflect the settings in your Slack app's configuration.

Slack App Credentials

Since we defined the route to our callback as /auth/callback in our routes file, you should use http://localhost:3000/auth/callback (or a different port if you're running Rails elsewhere) as the redirect_uri value. Note that you'll want to make this configurable when you deploy this to production.

You'll also want to add http://localhost:3000/auth/callback to the redirect URL field in your Slack app config panel:

Slack OAuth Settings

After we call oauth_access, we then update our current_user record's slack_access_token and slack_incoming_webhook_url attributes with the values in the API response. You might want to store them differently in your app, so I've added this purely for illustration. But you'll want to store them somewhere so you're able to access them when we post messages using the incoming webhook.

Send a message using the webhook

We've successfully authorized our Rails app to use the Slack API on behalf of our user. Now let's post a message using the incoming webhooks API!

For demonstration, let's build an endpoint at /post_message which posts the message "Hello, Slack!" into the user's Slack when we visit it.

First, add a route declaration:

# config/routes.rb
Rails.application.routes.draw do
  # ...
  get '/auth/callback', to: 'slack#callback'
  get '/post_message', to: 'slack#post_message'
end

We're going to use the Faraday gem as our HTTP client. Any HTTP client gem will do, since the incoming webhook is just a plain HTTP request. Add it to your Gemfile:

# Gemfile
# ...
gem 'faraday'

And add a new controller action to SlackController:

class SlackController < ApplicationController
  # ...

  def post_message
    conn = Faraday.new(url: current_user.slack_incoming_webhook_url)

    conn.post do |req|
      req.headers['Content-Type'] = 'application/json'
      req.body = { text: "Hello, Slack!" }.to_json
    end

    render text: "Posted to Slack!"
  end
end

First we create a new Faraday connection with the URL we captured in our callback action. Then, we post to the endpoint using a JSON request body. The payload of the request is formatted according to the specification in the Slack Incoming Webhooks documentation. Finally, we render some text to let the user know we posted to Slack.

We ought to do more error handling in the event Slack doesn't respond, but I'll leave that as an exercise for the reader.

Assuming everything is wired up, when you point your browser at http://localhost:3000/post_message, you'll find a new message waiting for you in Slack!

I had a tough time sifting through the Slack documentation to find a decent Rails walkthrough, so I hope this guide answers some of your questions.

Alias your common Ruby commands for a faster workflow

If you're a Rubyist, you probably use the likes of rspec, cucumber, rake, and other commands frequently. And it's likely that you might be running them using bundle exec to execute them in the context of your project's bundle. After finding I was spending a lot of time typing each of these commands, I added a few aliases to my shell config to speed up my workflow:

alias rsp='bundle exec rspec'
alias cuc='bundle exec cucumber'
alias rak='bundle exec rake'
alias mm='bundle exec middleman'

Paste these into your ~/.bashrc or ~/.zshrc, restart your shell, and now running an rspec test in the context of your bundle is as simple as:

rsp spec/models/banana_spec.rb

Have other useful aliases? Post them in the comments below!

Using Gulp to generate image thumbnails in a Middleman app

var gulp = require('gulp');
var imageResize = require('gulp-image-resize');

var paths = {
  images: "source/images/**/*"
}

gulp.task('images', function() {

    gulp.src(['source/images/**/*.png', 'source/images/**/*.jpg'])
        .pipe(imageResize({
            width: 538,
            height: 538
        }))
        .pipe(gulp.dest('tmp/dist/assets/images/538x538'));

    gulp.src(['source/images/**/*.png', 'source/images/**/*.jpg'])
        .pipe(imageResize({
            width: 1076,
            height: 1076
        }))
        .pipe(gulp.dest('tmp/dist/assets/images/1076x1076'));

});

gulp.task('watch', function() {
  gulp.watch(paths.images, ['images']);
});

gulp.task('default', ['watch', 'images']);
gulp.task('build', ['images']);

Fixing a Ruby crash using Middleman 4.1x with Ruby 2.3.0

I went to update a Middleman project's gems today. After I did, I noticed middleman server exited with the following error:

Assertion failed: (!STR_EMBED_P(shared)), function str_new_frozen, file string.c, line 1075.

This is an internal Ruby error generated from within its C source. I found a discussion about the topic in the Ruby bug tracker, but that wasn't much help.

Once I modified my Gemfile to use Ruby 2.3.1 and re-bundled my gems, middleman server worked just fine.

If you're running into this problem, try upgrading to a more recent Ruby and running Middleman on it. There's a chance this bug has nothing to do with Middleman and could be due to another gem, but I figured I'd mention it here in case someone else is having the same problem.

How to authenticate Instagram in a command line application

Instagram

Instagram uses OAuth to authenticate, meaning it can be kind of a drag to use its API if you don't want to build a web application. Building the simplest interface you can build to achieve your application's goals is one of the best ways to streamline your development process. And the simplest and cheapest interface is often the command line.

But because the OAuth handshake requires a web callback to operate, it can be cumbersome to build this authentication into a command line application. Below, I'll show you how to do it with only a little bit of annoyance.

Create an Instagram API Client

First, you'll want to go ask Instagram nicely for an API Client ID so you can get access to the Instagram API. Go to their developer portal and click 'Manage Clients' to add a new one.

When asked for URL's, feel free to use non-existent domains. I use guilded.dev for mine.

In the Security tab, be sure to uncheck "Disable implicit OAuth". This will allow you to connect to the API without requiring an explicit server-side post, meaning we can hijack the access token from the callback URL:

Uncheck 'Disable implicit OAuth'

Make a Firm Handshake

So what are you to do when you can't redirect your terminal window to Instagram so you can authorize your account? A little bit of copy-pasta. Here's what we're going to do:

  1. Generate an Instagram authorization URL and ask the user to paste it into their browser.
  2. The user will authenticate their Instagram account like usual. They'll be redirected to our dummy Redirect URI.
  3. We'll prompt the user for their newly-issued access token. Because we unchecked "Disable implicit OAuth" in our Instagram client configuration, the access token will be appended to the redirect URI. We'll ask them kindly to copy and paste it into the terminal.
  4. We'll be authenticated to Instagram in the terminal!
require 'instagram'

Instagram.configure do |config|
  config.client_id = "YOUR CLIENT ID"
  config.client_secret = "YOUR CLIENT SECRET"
end

# Generate an Instagram authorization URL
puts "Visit the Instagram OAuth URL below to get started:\n"
puts "" + ::Instagram.authorize_url(
  redirect_uri: "http://guilded.dev/instagram/callback"
  response_type: 'token'
)

# Prompt the user for their newly-issued access token.
puts "Enter the access token at the end of the redirect URL.\nYou'll find it after the '#access_token=' in the URL."
access_token = gets.strip

# Create an Instagram client with the access token.
client = Instagram.client(access_token: access_token)

Now you should have an authenticated Instagram client. Use the Ruby Instagram API as usual:

for media_item in client.user_recent_media
  puts media_item.images.thumbnail.url
end

Store Your Access Token

Of course, requiring entering the access token each time we use our Instagram command line application is going to annoy our user. What if we could store the access token on the first authentication so we could use it for subsequent runs?

For this example, we'll store the access token in a file called .instagram-access-token. Depending on your application, you might want to use an existing YAML configuration file or another method.

require 'instagram'

# Configure the Instagram gem the same way we did above:
Instagram.configure do |config|
  config.client_id = settings.instagram_client_id
  config.client_secret = settings.instagram_client_secret
end

# If there's an access token saved to the file, then read it.
if File.exists?(".instagram-access-token")
  access_token = File.read(".instagram-access-token")
else
  # Otherwise, generate one
  puts "Visit the Instagram OAuth URL below to get started:\n"
  puts "" + ::Instagram.authorize_url(
    redirect_uri: "http://guilded.dev/instagram/callback"
    response_type: 'token'
  )

  # Prompt the user for their newly-issued access token.
  puts "Enter the access token at the end of the redirect URL.\nYou'll find it after the '#access_token=' in the URL."
  access_token = gets.strip

  # And save the token to the file for the next use:
  File.open(".instagram-access-token", 'w') do |file|
    file.write(access_token)
  end
end

# Create an Instagram client with the access token.
client = Instagram.client(access_token: access_token)

As you can see above, we first check to see if there's an access token saved in our .instagram-access-token file. If there is, we skip the handshake process altogether. If not, we initiate the handshake.

Note that for the purposes of simplifying the example, I've left out some error handling for invalid access tokens. You'll want to verify that the access token stored is valid and go through the handshake process again if you cannot connect.

Conclusion

Building a command line application for Instagram is fairly easy, assuming you're able to build the authentication in a way that doesn't confuse your user. If you're just building a tool for personal use, this is a great way to create real value without incurring the burden of building a full-blown web application.

If you have questions or if something is unclear, please leave a comment below and I'll do my best to answer you.