Writing a Custom Cop for RuboCop

I wrote my first custom cop for RuboCop recently and wanted to document it here. There are a lot of really high quality resources on writing custom cops for RuboCop out there. Here are the ones I looked at:

At my current job we're using the excellent Money gem for dealing with money and currency conversion. The gem provides two different ways of initializing a Money object: Money.new(...) and Money.from_cents(...). Both methods take a number of cents and create a Money object, but I strongly prefer Money.from_cents because it's much clearer what kind of argument it is expecting. So my team decided it would be nice to have a RuboCop cop to enforce that we always use Money.from_cents instead of Money.new.

Here's the code for the cop I ended up writing:

module Cops
  class DisallowMoneyNew < RuboCop::Cop::Base
    def_node_matcher :using_money_new?, <<~PATTERN
      (send (const nil? :Money) :new $...)
    PATTERN

    def on_send(node)
      return unless using_money_new?(node)

      add_offense(node, message: "Use Money.from_cents instead of Money.new")
    end
  end
end

Simple enough! There's definitely more RuboCop allows you to do also, like adding code for autocorrect which would probably be pretty straightforward in this case.

I found writing the node matcher and understanding node pattern rules to be the most confusing part of it, but luckily there are a lot of examples out there to go off of. Overall I found the process simple enough that I think writing custom cops like this is totally worth the time. It's a really great way to enforce patterns that your team agrees on beyond just documenting them somewhere and hopoing no one forgets.

One more thing — as part of this I created a pull request to add a new initializer Money.from_dollars to the Money gem. I think it would be a nice complement to the Money.from_cents intiailizer and clearer than the existing Money.from_amount intiailizer. The repo doesn't seem super active these days, but hopefully they accept it!