How Custom Ink unifies its code base across projects.
I'm sure that every developer is familiar with the concept of a coding guide to ensure that everyone working on a project is writing the same code. This ensures that the code style remains consistent, and the overall quality of the code is guaranteed. Of course this doesn't come without its share of problems and difficulties.
As it stands right now, Custom Ink has just over 800 different repositories on GitHub, the majority of them being Ruby projects. As we have multiple teams working across these projects, we need to come up with some way to keep a uniform code style.
Almost all of our Ruby projects use RuboCop through CodeClimate. While this is a good step in the right direction, it does not guarantee that all projects also adhere to the same style guidelines. After all, nothing prevents an engineer from disabling a rule, changing its configuration or outright ignoring the messages from CodeClimate.
As mentioned above, we use CodeClimate. We, however, do not always see eye-to-eye with the warnings and issues raised by CodeClimate. Mainly because a team might have a different view on things, there's a reason why we want to deviate or something is technically not possible. There are also technical limitations imposed by CodeClimate that make it hard to do certain technical solutions.
Inky is the mascot of Custom Ink. Our lovely octopus is a part of the company, and seeing we actually depend on RuboCop, I came up with the name InkyCop. So what is InkyCop exactly?
InkyCop is a gem developed for internal usage at the moment, where we have created several configuration files:
customink.yml
file that contains the general configuration for all RuboCop cops, regardless of Ruby versionrubocop.yml
versions for that Ruby versionrubocop-rails
and rubocop-rspec
This allows us to create a matrix of configurations that we can tweak to support all Ruby versions that are supported by RuboCop, and used inside the company. Of course, we need to work on getting everything up to date, but this is a tremendous amount of work that will take time, and it's easier to support the version through RuboCop.
Our merchandising system is actually the first system to rely on InkyCop! Its configuration file looks as follows:
# The load order is first those defined by inkycop, then everything in the todo file,
# finally whatever is appended later in this file.
inherit_from: .rubocop_todo.yml
inherit_gem:
inkycop:
- config/rspec/2.3/rubocop.yml
- config/customink.yml
That's it! nothing more needs to be added or configured to use all of RuboCop features, and rely on the shared configuration that's defined in our Gem.
Getting InkyCop successfully integrated was no easy-feat. There's actually quite a few technical details that need to be kept in mind when creating this gem. Even though the gem in itself is nothing more than a simple wrapper around RuboCop with custom configuration files:
inkycop
gem is not checked out.bundle exec
or with a binstub.rubocop.yml
and not .rubocop.yml
inside the gem.There is a subtle difference on how RuboCop interprets paths, depending on how your configuration files are named. This took some debugging as well as diving deep into the documentation of RuboCop to understand and figure out.
.rubocop.yml
: All paths are treated as relative of the file's locationrubocop.yml
: All paths are treated as relative of the location RuboCop is running fromWhile this seemed to work properly at first, it becomes more tricky when files start inheriting from one another and/or files are combined as we do. That's why we made this an integral part of our documentation to make sure this information is well known.
The next step is get all projects to migrate slowly to using InkyCop, so we can start tweaking our configuration. There's going to be a lot of discussion on whether we will follow the standard set by RuboCop, or tweak a Cop to our liking. In the end, all teams will have to agree to it, and roll out with InkyCop to unify the coding guidelines across the entire code-base.