DEV Community

Cover image for How I replaced a Rails app with a few dozen lines of Ruby
Nick Holden
Nick Holden

Posted on

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 UI

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 "com.github.actions.name"="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"]
Enter fullscreen mode Exit fullscreen mode

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 = PushEvent.new(File.read(ENV.fetch("GITHUB_EVENT_PATH")))

if push_event.modified?(file_path)
  puts "#{file_path} was modified"
  exit(0)
else
  puts "#{file_path} was not modified"
  exit(1)
end
Enter fullscreen mode Exit fullscreen mode

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.

Modified File Filter in GitHub Actions’ workflow editor

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.

Discussion (10)

Collapse
andrei_says profile image
Andrei Andreev

Love this. We live in exciting times, the internet is rich with pluggable tools resembling larger LEGO pieces.

Appreciate the reminder to focus on the problem, remaining open minded on tooling.

Collapse
nholden profile image
Nick Holden Author

Well said! I totally agree. Thanks, Andrei!

Collapse
fdutey profile image
Florian Dutey

The first problem looks a lot to me like the following => Your DB = Your API.

That's the very big problem with rails. Everything in rails lead you to think that data in DB, data in API responses (through render json: @model) and data in API requests (through: Model.new(params[:model])) is the same.

Well it's not.

That's why, even if many voices in rails community are against

1) Template views (like json.jbuilder) with aliasing every property manually should always be favored over render json: @model or other magical things (if you have rest API, or you can use graphql, it's even better)
2) Requests should be handled by form objects then form objects would translate requests data into model data, acting as a proxy, leaving the API intact if you rename some columns (or you can use graphql)

My point is: you shouldn't have to monitor db/structure.sql because changing the SQL structure should not have any impact on API contract.

Collapse
nholden profile image
Nick Holden Author

Ok! I disagree and still like Rails. Interesting take, though.

Collapse
cescquintero profile image
Francisco Quintero 🇨🇴

Well, I agree with the final lesson. However, sometimes there's no solution than to do more job than the expected.

Not sure about the dates but could it be GitHub actions weren't available by the time you coded DiffAlert?

Anyway, both things are nice and I can be sure you learnt something real good while doing DiffAlert, didn't you?

There's this book called "Building Software Products in a Weekend" and the guy basically says that you should first look for something built(do an intensive search), if it exists buy it(if costs money), then if doesn't exist, build it. But again, surely GH Actions were not a thing by then.

Collapse
nholden profile image
Nick Holden Author

Thanks, Francisco!

Not sure about the dates but could it be GitHub actions weren't available by the time you coded DiffAlert?

That's true: GitHub Actions weren't around when I worked on DiffAlert. I bet there was another solution out there at the time that would have been less code and infrastructure than a Rails app though. I wonder if I could have pointed GitHub webhooks at Zapier or another similar service.

Anyway, both things are nice and I can be sure you learnt something real good while doing DiffAlert, didn't you?

Agreed! Yes, I had a blast building DiffAlert, and I learned a whole bunch in the process.

Collapse
ewanslater profile image
Ewan Slater

Interesting post, and absolutely agree with the overall message "write less code and maintain less infrastructure".

Going back to the original problem - the dashboard breaking because of the schema change - did you consider using database views (at least for the dashboard)?

That way you could have made the schema change (including updated views) and the dashboard contract would have been maintained.

The scenic gem even lets you do views "the rails way".

Collapse
nholden profile image
Nick Holden Author

Thanks, Ewan! I hadn't considered database views, but that's a great point -- I'll definitely think of them the next time I encounter a similar problem.

Collapse
zimski profile image
CHADDA Chakib

Love this and WISE conclusion.
Thanks for sharing

Collapse
aemadrid profile image
Adrian Madrid

Thanks for the article! We will look into doing something just like you did.