Julia Evans

Day 32: A Rails model that doesn't use the database with ActiveHash

Hello! RC took a few weeks ago, and it’s back now! I’m batching these posts a few days at a time, so here’s what happened on Tuesday.

deployed docker-compose to production

On Monday I set up a dev environment with Docker Compose (blog post), but I wasn’t sure if it would work well in production.

My “production” in this case is a single DigitalOcean droplet, and I thought that setting up a new server to use docker-compose instead of what I already had would be a lot of work.

It turned out to be easier than I thought and it only took a couple of hours and I felt like my new setup was WAY more robust afterwards.

the steps to setting up docker-compose in production

I think all of my steps were:

  • use DigitalOcean’s one-click Docker droplet to start an instance
  • install net-tools so that I could use netstat
  • install golang so that I could build go programs outside the container (though I’ll probably switch to doing this in a container soon, it’ll be better)
  • clone my Github repo to a bare repository on the droplet (git clone https://github.com/my/repo --bare)
  • set up a post-receive hook (like in this example), here’s my post-receive hook
  • write a docker-compose-prod.yml (you can see it here) with a slightly different configuration for production
  • Add my server as a remote, like git remote add railsbox [email protected]:/my-repo.git
  • run git push railbox
  • make a dump of my database with pg_dump and restore it in the new database
  • copy some secrets that aren’t in git over to the server (SSH keys, the Rails secret master key, and some secret environment variables)
  • fix a bunch of miscellaneous problems with my configuration files to make them actually work
  • update my DNS records
  • done!

This is pretty far from “just click one thing and you’re done” but I feel like if I had to do it again because I lost my production server it wouldn’t be too bad. It’s definitely much better than “just create a lot of systemd files by hand” which is what I was doing before.

making it easier for me to edit my puzzles

All of this docker-compose stuff wasn’t actually my goal for the day, though!

My real goal was: I have this sort of puzzle game, and I was storing the definitions for my puzzles (basically the title and a cloud-init.yaml file) in the database.

This really wasn’t working for me because I needed to make a lot of updates to the puzzles (at least to start) and having to do it through a web interface felt way too slow.

Andther problem I was having was that the puzzles tables wasn’t synced between dev and prod, which made it hard to test.

enter ActiveHash!

ActiveHash is a Ruby gem that let you just define all of your data for a model in a hashmap or file instead of having a database table.

This feels good for now because I only have like 6 puzzles and so having them all in a file makes it way easier to edit them.

The main thing that makes me feel nervous about it is that right now I’m entering the puzzle IDs manually (like id: 1) and I need to make sure to not accidentally reuse/change the IDs. This is important because some other fields in the database reference the puzzle ID.

my current Puzzle class

Here’s what my Puzzle with ActiveHash looks like. It’s really simple.

class Puzzle < ActiveHash::Base
  def to_param
    "#{id}-#{slug}"
  end

  def finished?(user)
    PuzzleStatus.where(user_id: user.id).where(puzzle_id: self.id).first&.finished || false
  end

  def cloud_init
    File.read("puzzles/#{group}/#{slug}/cloud-init.yaml")
  end

  self.data = [
    {
      id: 1,
      group: "networking",
      slug: "connection-timeout",
      title: "The Case of the Connection Timeout",
      published: false,
    },
    ... more data here

I can still use some ActiveRecord methods!

Things like belongs_to and has_many don’t work (which kinda makes sense to me), but I can still do Puzzle.find(id) to find a puzzle by its ID, so I didn’t have to change too much of my code.

And because I’m using ActiveRecord methods, if I ever want to switch back to using a database to manage them, it should be pretty easy!

what I had to do to switch to this class

I needed to:

  • remove all of the edit/update/create code from my Puzzles controller
  • write a migration to drop the puzzles table from the database
  • remove the puzzles.yml fake data from my tests (because it was inserting that data into the database, which was failing)

and probably some more things that I’ve forgotten

that’s all!

I felt pretty happy about both these changes.

Docker Compose: a nice way to set up a dev environment Day 33: pairing is magic and beautiful git diffs