Hardscrabble 🍫

By Max Jacobson

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

29 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

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
# 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
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

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

assigning values to multiple variables in one line

27 Jun 2015

Why would you write this:

a = 1
b = 1

When you could write:

a = b = 1

A few reasons:

  1. Maybe you don’t know about this syntax
  2. Maybe you don’t mind writing out two lines
  3. Maybe you’re concerned about having two references to the same data, as explained in this StackOverflow post

I recently saw code that looked like this, which was disabling some loggers:

Something.logger = OtherThing.logger = nil

And I was kind of confused and amazed. I know about this multiple assigning syntax, but this looked kind of different. In the earlier example, we were assigning a value to a simple local variable, but in this case we were calling a setter method instead.

Something like:

class Dog
  attr_reader :name, :family

  def initialize(name)
    @name = name

  def family=(family_name)
    @family = family_name

milo = Dog.new("Milo")
lola = Dog.new("Lola")

milo.family = lola.family = "The Jacobsons"
p [milo, lola]
# [#<Dog:0x007faf6115b158 @name="Milo", @family="The Jacobsons">, #<Dog:0x007faf6115b108 @name="Lola", @family="The Jacobsons">]

This works because Ruby gives you this syntactic sugar when you write a something= method, it lets you put a space before the = when calling the method. And that applies in this context too. Kind of neat.