Hardscrabble 🍫

By Max Jacobson

Making FactoryBot.lint verbose

10 Feb 2021

Here’s a quick blog post about a specific thing (making FactoryBot.lint more verbose) but actually, secretly, about a more general thing (taking advantage of Ruby’s flexibility to bend the universe to your will). Let’s start with the specific thing and then come back around to the general thing.

If you use Rails, there’s a good chance you use FactoryBot to help you write your tests. The library enables you to define “factories” for the models in your system with sensible default values.

FactoryBot has a built-in linter, which you can run as part of your CI build. It will try to identify any factory definitions which are faulty.

At work, we run this as a Circle CI job. It almost always passes, but every now and then it catches something, so we keep it around.

Recently, it started failing occasionally, but not because of anything wrong with our factories. Instead, it was failing because it was just… slow. Circle CI is happy to run things like this for you, but it gets antsy when something is running for a while and printing no output. Is it stuck? Is it going to run forever? If something is running for 10 minutes with no output, Circle CI just kills it.

Our factory linter apparently takes ten minutes now, we learned.

So, what to do about that?

Per that support article, one option is to just bump up the timeout. That’s easy enough. We could tell Circle CI to wait 15 minutes. In a year or two, maybe we’ll need to bump it up again, assuming such pedestrian concerns continue to dog us then, while we drive around in our flying cars.

Another option would be to just stop running it. It’s useful-ish but not essential. That’s easy enough.

Another option would be to configure the linter to print verbose output while it’s running. If we could do that, then we’d get multiple benefits: first of all, Circle CI would be satisfied that it is not stuck, and that it is making progress, and that it might eventually finish, even if it takes more than ten minutes; but also, having some output might be interesting and useful, no? Hmm. I pulled up the FactoryBot docs and saw an option verbose: true, but it didn’t seem to be what I wanted:

Verbose linting will include full backtraces for each error, which can be helpful for debugging

I want it to print output even when there are no errors. I didn’t see anything like that.

Imagine a ruby file with this stuff in it:

require 'factory_bot'

class Dog
  attr_accessor :name

  def save!
    sleep 4
    true
  end
end

class Band
  attr_accessor :name

  def save!
    sleep 4
    true
  end
end

FactoryBot.define do
  factory :dog, class: Dog do
    name { 'Oiva' }
  end

  factory :band, class: Band do
    name { 'Bear Vs. Shark' }
    albums { 2 }
  end
end

factories = FactoryBot.factories
FactoryBot.lint(factories)

There’s actually a bug in our Band factory: it references an attribute called albums which does not exist in our model code. The linter will catch this.

Looking at that last line, it looks like we just pass in the list of factories, and then presumably it will loop over that list and check them one-by-one.

Looping over things is a really common thing in Ruby. Anything that you can loop over is considered “enumerable”. Arrays are enumerable. Hashes are enumerable. When you query a database and get back some number of rows, those are enumerable.

A list of factories is enumerable. Hmm.

Let’s try writing our own enumerable class, to wrap our list of factories. We’ll call it ChattyList. It’ll be a list, but when you loop over it, it’ll chatter away about each item as they go by.

In general, if you’re calling a method and passing in one enumerable thing, it would also be fine to pass in some other enumerable thing. It’s just going to call each on it, or reduce, or something like that from the Enumerable module.

class ChattyList
  include Enumerable

  def initialize(items, before_message:, after_message:, logger:)
    @items = items
    @before_message = before_message
    @after_message = after_message
    @logger = logger
  end

  def each
    @items.each do |item|
      @logger.info @before_message.call(item)
      yield item
      @logger.info @after_message.call(item)
    end
  end
end

factories = ChattyList.new(
  FactoryBot.factories,
  logger: Logger.new($stdout),
  before_message: -> (factory) { "Linting #{factory.name}" },
  after_message: -> (factory) { "Linted #{factory.name}" },
)

FactoryBot.lint(factories)

When I run the script, the output looks like this:

I, [2021-02-10T22:43:03.676359 #57462]  INFO -- : Linting dog
I, [2021-02-10T22:43:07.678616 #57462]  INFO -- : Linted dog
I, [2021-02-10T22:43:07.678712 #57462]  INFO -- : Linting band
I, [2021-02-10T22:43:07.679373 #57462]  INFO -- : Linted band
Traceback (most recent call last):
        2: from app.rb:59:in `<main>'
        1: from /Users/max.jacobson/.gem/ruby/2.6.6/gems/factory_bot-6.1.0/lib/factory_bot.rb:70:in `lint'
/Users/max.jacobson/.gem/ruby/2.6.6/gems/factory_bot-6.1.0/lib/factory_bot/linter.rb:13:in `lint!': The following factories are invalid: (FactoryBot::InvalidFactoryError)

* band - undefined method `albums=' for #<Band:0x00007ff7b9022890 @name="Bear Vs. Shark"> (NoMethodError)

Nice! As the linter chugs thru the factories, it prints out its progress. With this, Circle CI will see that progress is happening and won’t decide to kill the job. This option wasn’t offered by the library, but that doesn’t have to stop us. Isn’t that fun?

By the way: that might be a good option to add to FactoryBot! Feel free, if you’re reading this, to take that idea.

heredocs in ruby

03 Feb 2021

I’ve recently been writing a lot of heredocs in Ruby. We have to talk about it.

what is the deal with heredocs?

It’s one of the ways to make a string. It looks like this:

def help
  <<TEXT
Help.
I need somebody
TEXT
end

help # => "Help.\nI need somebody\n"

The idea is that you have some all caps label on the first line (TEXT in that example), and then Ruby will look at the next line as the start of the string, and keep going until it sees that label again, and then the string is over.

It’s pretty similar to just using quotation marks like usual:

def help
  "Help.
I need somebody
"
end

help # => "Help.\nI need somebody\n"

One nice thing about the heredoc syntax is that you can use quotation marks in the middle of the string, and you don’t need to worry that you’re accidentally going to close the string.

That’s a pretty standard one, but there are a bunch of variations on the theme.

For example, this one is more common in my experience:

def help
  <<-TEXT
Help.
I need somebody
  TEXT
end

help # => "Help.\nI need somebody\n"

It looks a little nicer to indent the closing TEXT at the same level as the starting one, but that’s not allowed with standard heredocs. If you want to do that, you need to start the heredoc with <<- instead of <<.

It would look even nicer if you could indent the text of the string itself. Unfortunately, if you do that, it affects the actual value of the string:

def help
  <<-TEXT
    Help.
    I need somebody
  TEXT
end

help # => "    Help.\n    I need somebody\n"

No worries – they thought of that. You can use the “squiggly heredoc” syntax, which lets you write it like that without actually affecting the value of the string:

def help
  <<~TEXT
    Help.
    I need somebody
  TEXT
end

help # => "Help.\nI need somebody\n"

Most of the time, you should use a squiggly heredoc.

There is one last variation, which I’ve never seen in production code, but which I’ll share for completeness’s sake. This is the single-quote heredoc:

def help
  <<~'TEXT'
    Help.
    I need #{somebody}
  TEXT
end

help # => "Help.\nI need \#{somebody}\n"

When you put single quotes around TEXT – our heredoc delimiter in these examples – Ruby will treat the string like a single-quoted string rather than a double-quoted string. You know how in Ruby, if you want to use interpolation, you need to use double quotes?

"#{1 + 1}" # => "2"
'#{1 + 1}' # => "\#{1 + 1}"

Well, someday you’ll want to create a heredoc which behaves like a single quoted string (I don’t know why, to be honest) and you’ll be glad that you can.

why have I been writing so many heredocs recently?

At work, we use Rubocop to format our code. One of its rules, Layout/LineLength, checks that your lines aren’t longer than 120 characters. I think it’s a pretty good rule, and I’m gradually updating the existing code to follow it.

For the most part, it’s pretty straight-forward. Maybe you have a line that looks like:

foo(:a, :b, :c)

And you change it to

foo(
  :a,
  :b,
  :c,
)

Great, now it’s growing vertically instead of horizontally.

But what about lines that look like:

Rails.logger.info "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

What do you do with that?

Let’s throw a heredoc on it:

Rails.logger.info <<~MSG.chomp
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
MSG

It’s still quite long, but the Rubocop rule has a loophole: heredocs are fine. You can disable this loophole via configuration, but I don’t want to, I like it. As a reader, I know that when I’m looking at a heredoc, the whole thing is a string; even if part of it’s off screen, I’m not missing much, it’s just more string.

If we wanted to disable the loophole, we might write that as:

Rails.logger.info <<~MSG.squish
  Lorem ipsum dolor sit amet,
  consectetur adipiscing elit,
  sed do eiusmod tempor incididunt
  ut labore et dolore magna aliqua.
  Ut enim ad minim veniam, quis
  nostrud exercitation ullamco
  laboris nisi ut aliquip ex ea
  commodo consequat. Duis aute
  irure dolor in reprehenderit in
  voluptate velit esse cillum dolore
  eu fugiat nulla pariatur. Excepteur
  sint occaecat cupidatat non proident,
  sunt in culpa qui officia deserunt
  mollit anim id est laborum.
MSG

That uses the String#squish method in Rails, which squishes a multi-line string onto one line. Is that better? That’s between you and your God. I can go either way.

One unexpected benefit of using heredocs

Imagine this code:

def my_great_query
  "select count(*) from users"
end

my_great_query # => "select count(*) from users"

When you’re editing that code in your text editor, your editor is using syntax highlighting to help you. Maybe it’s turning all of the keywords orange, or all of the method names blue. This can help your eyes to scan thru the code, and can help alert you to syntax errors. But that string on the second line is just a string, and it looks like all of the other string literals. Your editor does not know that it is a fragment of SQL, and that it can apply its SQL syntax highlighting to the contents of that string. How could it know that?

Well, imagine if you wrote it like this instead:

def my_great_query
  <<~SQL.chomp
    select count(*) from users
  SQL
end

my_great_query # => "select count(*) from users"

Now your editor has a context clue it can use, and perhaps it will elect to apply SQL highlighting to that string. VS Code, for one, does. I only use VS Code sometimes, but it’s things like this that give me a little pop of delight and make me want to make it more of a habit.

some of the quirks of using heredocs

One thing that really must be said before putting a bow on this blog post is that heredocs are kind of … weird. Like, what if you want to call a method on a heredoc, like to reverse it? It kind of feels like you should put that .reverse all the way at the end, like you would for a normal string:

# this is invalid
def help
  <<-TEXT
    Help.
    I need somebody
  TEXT.reverse
end

help

Why is this invalid? Well, remember what I said at the beginning of this blog post

Ruby will look at the next line as the start of the string, and keep going until it sees that label again, and then the string is over

(Yes I’m quoting this blog post in this blog post. I’m pretty sure that’s allowed.)

I could have been more clear there but I didn’t want to be so clear that it was confusing: the string ends when Ruby sees a line that has that label and nothing else. If it sees TEXT.reverse, that does not satisfy that rule.

So you need to write it like:

def help
  <<-TEXT.reverse
    Help.
    I need somebody
  TEXT
end

help # => "\nydobemos deen I    \n.pleH    "

One last quirk, via my colleague Ian. What if you want to start two heredocs on the same line? You probably shouldn’t, but it is possible:

def help
  [<<~TEXT, <<~SQL]
    Help.
    I need somebody.
  TEXT
    select * from lyrics
  SQL
end

help # => ["Help.\nI need somebody.\n", "select * from lyrics\n"]

Whoa.

sindresorhus/pure is such a good zsh prompt

02 Feb 2021

Given how much time I spend in a terminal, typing things and hitting enter, I think it’s a good idea to keep the vibe in my shell nice. For the first like seven years of my coding career, I felt it was important that I design and maintain my own prompt. There’s evidence of this on my blog and in my dotfiles repo:

  • In December 2012, when I used this character ϡ (???)
  • In June 2013, when I used a little Ruby script to make it print a random emoji each time
  • In June 2015, when I wrote a shell script to display some git information about the current repository
  • In June 2016, when I kept the random emoji, but rewrote the script in Rust
  • In December 2017, when I finally got rid of the emoji, because I was using Linux at work and couldn’t figure out how to display emoji in my terminal emulator
  • In January 2019, when I rewrote the git shell script in Rust, which I thought would make it faster, but actually made it slower, but by then I was too stubborn and just kept it

On some level, I felt like my shell prompt was an avenue for self expression. I took some pride in that. No offense if you don’t care about yours, it’s not a judgment thing. I’m just trying to establish some stakes here.

I found myself annoyed by how long my prompt took to render, especially when working in large git repositories. I spent some time optimizing it. I disabled some of the functionality; for example, I updated the prompt to display my current git branch, but ripped out the “dirty checking” which changes the color of the branch when there are uncommitted changes. I missed that functionality and brought it back.

Eventually, just as an idea, I decided to see what was out there. I googled around a little bit, and found sindresorhus/pure, which bills itself as a “Pretty, minimal and fast ZSH prompt”. That’s basically what I want.

I installed it a few months ago, and… well, shit. It’s very good.

Here are a few things that are great about it.

When your last command failed, the prompt turns red.

When your last command took a while, the prompt automatically displays how long it took. You don’t need to have the thought “hm, that felt slow, was that slow? Should I re-run it with time?”

The prompt tells you when your branch is behind the remote, and you should might want to pull. That means it automatically fetches, so that it can know that. At first that seemed kind of crazy, that rendering my prompt would have side effects on my repository (“How dare you, prompt?” was my gut reaction), but I’ve come to really appreciate it. It’s the best kind of automation, in that it becomes just one less thing I need to worry about. That requires building some trust, but it did.

It renders super fast because it does a lot of its work asynchronously, like checking the git status. It uses this script called mafredri/zsh-async to do that. The effect is pretty novel: the prompt renders right away, and then (sometimes) it changes a half second later. It’s a lot like an asynchronous request in a website, which fetches data and displays it when it’s ready. I skimmed the readme of that repo and I have no idea how the hell it works, but I basically don’t care, I’m happy to let it be some magic.

There’s only one thing that I miss from my days of customization. Before I used pure, my prompt looked like:

hardscrabble.github.io main*

All on one line. The first bit is the name of the folder. Then the name of the branch – printed in red, and with an asterisk, to indicate that there are uncommitted changes.

Imagine that you were at that prompt, and then you ran cd _posts. What should it display then? It would be pretty intuitive if it displayed this:

_posts main*

In my days of customization, though, my prompt would have displayed:

hardscrabble.github.io/_posts main*

The idea being that I wanted to know two pieces of information:

  1. what repo am I in?
  2. what path am I at, in that repo?

To achieve that, I needed to write some clumsy, but workable Rust.

By contrast, pure displays the absolute path to the working directory:

~/src/gh/hardscrabble/hardscrabble.github.io/_posts main*
❯

Which is…fine.

In summary, be like me: get over yourself and use pure.