How I replaced a Rails app with a few dozen lines of Ruby

A couple years ago, I broke a dashboard at work.
As part of building a new feature, I changed our database schema. I carefully laid out a plan to add some new columns and migrate existing data. I made sure the changes to the database would work with both new and old application code and the feature could be deployed without any downtime.
I forgot to consider that our application wasn’t the only consumer of our data. Our data team maintained dashboards that the company used to inform product decisions and make financial projections. Those dashboards expected our database schema to look a certain way, and when I made the changes without talking to the data team, one of those dashboards broke.
After we fixed the dashboard, I thought about how I could prevent myself from making the same mistake in the future. I decided needed a reminder each time I made a change to our database schema. To get that reminder, I reached for the tool I was most comfortable with and spun up a new Rails app.

There’s a lot to a Rails app

My goal with the app, DiffAlert, was to send an alert to Slack each time someone made a change to db/structure.sql on the master branch of our company’s application. I saw that GitHub had webhooks and that the push event sent along data about commits, including which files were changed.
In a few hours, I spun up the app, created an endpoint for the GitHub webhook events, and wrote the code to tell whether or not db/structure.sql changed in a given push. DiffAlert’s core logic was complete. Then the real work started.
Next, I had to figure out how to send an alert to Slack. We were already using Slack’s Email app, so I signed up for a new Mailgun account and created an email template with Action Mailer. Then I had to deploy DiffAlert somewhere, so I created a new Heroku application and did some tweaking to get everything properly configured with my workflow.
Besides db/structure.sql, we’d also want to monitor a few other files in our codebase, so I started building a UI to configure alert settings. I’d need authentication, so I designed the data model and created login and sign up forms. Then I needed another view to show all the alerts, and I needed forms to create new alerts and edit existing ones. I needed background jobs so that longer processes like webhook parsing and email sending wouldn’t hold up regular web requests.

DiffAlert chugged along for about a year and a half, reminding my team in Slack when we made changes to our database schema. Each time, we reached out to the data team and didn’t break any more dashboards.
DiffAlert was a side project, so when I left the company, they needed to decide if they would continue sending a former employee their GitHub metadata, maintain their own instance of DiffAlert, or stop receiving alerts. They understandably decided to stop receiving alerts.

GitHub Actions helped me focus on the problem

I work for GitHub, and when I learned about GitHub Actions, I wondered if I could replace DiffAlert with a single Action. A GitHub Action is code, written in any language, that runs in a Docker container when a specified event happens in a repository. I saw that push events could trigger GitHub Action workflows. I also saw that there was a GitHub Action that could post messages to Slack. I started writing Modified File Filter.
First, I needed a Dockerfile. I wanted to write my Action in Ruby, so I used the FROM instruction to create a Docker container from an official Ruby base image. I wrote some LABEL instructions so that my Action would show up correctly in the GitHub Actions visual workflow editor. Finally, I specified which folders the Docker container would need access to and pointed the ENTRYPOINT at an executable Ruby script.
# Dockerfile

FROM ruby:2.6.0

LABEL “"="Modified File Filter"
LABEL "com.github.actions.description"="Stops a workflow unless a specified file has been modified."
LABEL "com.github.actions.icon"="filter"
LABEL "com.github.actions.color"="orange"

ADD bin /bin
ADD lib /lib

ENTRYPOINT ["entrypoint"]

The executable Ruby script delegates most of the work to a plain old Ruby class, PushEvent, which parses the event data from GitHub and answers whether a file at a specific path is modified. When a push event modifies the file, the Action exits with a 0 status to trigger the next Action in a workflow. When a push event doesn’t modify the file, the Action exits 1 to halt the workflow.
# bin/entrypoint

require_relative "../lib/push_event"

file_path = ARGV.first
push_event ="GITHUB_EVENT_PATH")))

if push_event.modified?(file_path)
puts "#{file_path} was modified"
puts "#{file_path} was not modified"

I didn’t need to configure emails or figure out how to integrate with Slack since I could lean on the existing GitHub Action for Slack. I didn’t need to design any UI or authentication because that was all handled by GitHub. I didn’t need to configure databases or background jobs. I didn’t need to spin up a Heroku application and set up deployments.

Starting smaller

Rails has a reputation for being a great framework for validating ideas. You can use Rails to build a blog in 15 minutes! Because I love working with Rails, I often reach for it first when I have an idea.
But even with all the magic that Rails provides, most apps need a whole bunch of things — like authentication, UI, background jobs, email sending, deployment — that aren’t unique to my idea. Next time I have an idea, I’ll look for ways to write less code and maintain less infrastructure, at least to get started.
Cover image by Iain Farrell on Flickr.