hardscrabble 🍫

By Max Jacobson

Psst. Check out my RubyConf 2017 talk, There are no rules in Ruby.

blog posts

todo_lint: a new ruby gem to help prevent comments from stagnating

08 Jul 2015

I made another gem! Here it is: https://github.com/maxjacobson/todo_lint

Last week, I wrote about required_arg, my new gem for adding required keyword arguments to Ruby 2.0.0. I actually made that, quickly, while making this one, which took several hours longer.

What does it do?

It analyzes your code and warns you about TODO comments. You can run the script automatically as part of your continuous integration (CI) build process, and it will fail your build if your TODOs are troublesome. It suggests a workflow whereby your TODO comments must be annotated with a due date, and that due date must be in the future. So you can write little notes to self in your code, but you cannot forget them, because one day your build will start failing. At that point, you can do what you’ll do; maybe you’ll snooze the todo due date into the future; maybe you’ll pull the idea out into your feature/ bug tracker; maybe you’ll just delete it in acknowledgment that you’re never gonna do it. Up to you.

It’s going to be very annoying, and hopefully that will be a good thing.

Adding it to a Ruby project is pretty simple. Here’s what it looks like to add it to a gem: https://github.com/maxjacobson/film_snob/pull/85

At work, I’m coordinating a gaggle of interns working on a new project, and I asked them to be guinea pigs for me and include this gem in their project and CI workflow. They obliged, but unfortunately it immediately didn’t work at all. I didn’t realize it, but our CI service was bundling all dependencies into the same folder as the app, and then todo_lint was checking every file from every Ruby dependency for todos. We don’t want to check all those files, because they’re out of our control. I realized we would need some kind of configuration to allow excluding by folder or maybe even pattern, so I jotted it down in a GitHub issue and told the intern to forget about it; the guinea pig was dead!

Or … was it? Awesomely, she took the time to make a pull request to todo_lint, making it user-configurable; specifically, users can now exclude file patterns like vendor/**. Excellent! The guinea pig lives.

If you’d like to help improve the project, take a look at the remaining open issues and feel free to claim one or add your own. I want my projects to be friendly toward contributors. Awesomely, bundler now suggests including a code of conduct when you create a skeleton gem with, eg, bundle gem todo_lint, and I’ll do my best to uphold that.

Join me in being a scold. It’s fun.

required keyword arguments in Ruby 2.0.0

30 Jun 2015

TLDR: I made a gem, required_arg which offers a workflow for requiring keyword arguments in Ruby 2.0.0, which doesn’t support them on the language level.

In March, we looked at Ruby keyword arguments, and noted a curious tension.

Sometimes you want to use keyword arguments, and you don’t want to come up with a default value for that keyword. You kind of want to require the keyword. You can in Ruby 2.1+, but Ruby 2.0.0 is in the awkward position of having keyword arguments but not being able to require them.

Here’s what you can do in Ruby 2.1:

class Dog
  def initialize(name:)
    @name = name
  end
end

Dog.new(name: "Milo") #=> #<Dog:0x007fc404df9f10 @name="Milo">
Dog.new #=> an exception is raised: ArgumentError: missing keyword: name

That’s great! You don’t need to write any additional code, and Ruby will enforce that your method is called with the keyword arguments you require. This gives you flexibility to design interfaces which take advantage of the flexibility and clarity of keyword arguments, but still establish some expectations for how the method will be called.

Here’s what happens when you do the same in Ruby 2.0.0:

class Dog
  def initialize(name:)
    @name = name
  end
end
# dog.rb:2: syntax error, unexpected ')'
# dog.rb:5: syntax error, unexpected keyword_end, expecting end-of-input

Syntax error!

Here’s what I suggest doing now:

  1. Upgrade to a newer version of Ruby
  2. If you can’t, try this:
# gem install required_arg
require "required_arg"

class Dog
  def initialize(name: RequiredArg.new(:name))
    @name = name
  end
end
Dog.new(name: "Milo") #=> #<Dog:0x007fc404df9f10 @name="Milo">
Dog.new #=> an exception is raised: missing keyword: name (ArgumentError)

Close enough!

If your app is stuck on Ruby 2.0.0 or you’re making a library which supports Ruby 2.0.0, maybe you’ll find this useful. Let me know if you do.

Here’s the entire source for the gem:

class RequiredArg
  def initialize(name = nil)
    msg = name.nil? ? "missing keyword" : "missing keyword: #{name}"
    raise ArgumentError, msg
  end
end

Pretty simple, and kind of fun. It’s just a little cherry bomb class. The moment you instantiate it, it blows up. Make it the default value for a keyword argument, and the moment you forget a keyword argument, the default will be used and the expression will be evaluated for the first time. It’s cool that the default values are lazily evaluated because it allows for things like this.

Check out the gem: https://github.com/maxjacobson/required_arg