At Memory.ai, we started using RuboCop heavily. This is a story of how we integrated RuboCop into our existing app. This is not an introductory post to RuboCop. Check out what RuboCop is before diving into our experience report. We started with , , and gems. We enabled all cops by default, this was our initial configuration in rubocop rubocop-performance rubocop-rails rubocop-rspec rubocop.yml. - rubocop-rspec - rubocop-rails - rubocop-performance . - - - - - - - require: AllCops: EnabledByDefault: true TargetRubyVersion: 2.6 3 Exclude: 'app/views/**/*' 'db/**/*' 'bin/**/*' 'csv/**/*' 'slate/**/*' 'vendor/bundle/**/*' 'node_modules/**/*' If you enable all the cops that come with RuboCop in an existing project, you will get tons and tons of warnings and offenses. This was pretty evident when we ran after adding above configuration. rubocop Work in progress For existing projects, there is a better way to integrate RuboCop. RuboCop provides a file which records all the offenses from our codebase. We can fix these offenses one by one as we touch that particular piece of code. So we don't have to fix everything to start with. .rubocop_todo.yml To start using the , perform following steps. .rubocop_todo.yml bundle exec rubocop --auto-gen-config Added inheritance from . Phase of : run Metrics/LineLength cop Inspecting files CC....C..C..C...C..CCC..CC....CC..C..C files inspected, offenses detected Created .rubocop_todo.yml. Phase of : run all cops Inspecting files CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC files inspected, offenses detected Created .rubocop_todo.yml. `.rubocop_todo.yml` in `.rubocop.yml` 1 2 38 38 40 2 2 38 38 104 You will see similar output but number of offenses might be overwhelmingly large or extremely less depending on the state of your codebase :) This command did three things. Inherited .rubocop.yml from .rubocop_todo.yml(We will come back to it later) Ran rubocop on all of our code Generated a new file .rubocop_todo.yml Let's see of the . It has recorded all the offenses present in our code. Let's see example of one of the cop for better understanding. contents .rubocop_todo.yml # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: TreatCommentsAsGroupSeparators, Include. # Include: **/*.gemfile, **/Gemfile, **/gems.rb Bundler/OrderedGems: Exclude: - 'Gemfile' This little piece of code tells us that our codebase has one offense for cop and how we can fix it. But more importantly, it marks the file where this offense is present as from the list of files on which RuboCop will be run. Bundler/OrderedGems excluded What does this mean? Well let's try to run rubocop on the entire project now. ▶ bundle rubocop Inspecting 38 files ...................................... 38 files inspected, no offenses detected exec Woah! Our code is clean, it passes all the cops, time for party! Well, not really. This is where the comment needs to be looked at. Inherited .rubocop.yml from .rubocop_todo.yml ▶ cat .rubocop.yml inherit_from: .rubocop_todo.yml records all the offenses of our codebase and also excludes those files from the list on which rubocop will be run. .rubocop_todo.yml inherits from so it also excludes those files from the list on which rubocop will be run. .rubocop.yml .rubocop_todo.yml When we run , it picks up configuration from which in turn picks up the configuration from which has excluded all the files which have offenses. All of this results into a green output from command. bundle exec rubocop .rubocop.yml .rubocop_todo.yml bundle exec rubocop So we still have offensive code that RuboCop does not like, but we have a strategy to fix it incrementally rather than fixing it all at once. Pro Tip: Always generate the .rubocop_todo.yml when you are integrating Rubocop into an existing project. Git workflow Next natural step was to act on the file and fix the offenses as we touch the existing offensive code and make sure that the new code that we write is following the cops. .rubocop_todo.yml We decided to add a git hook which will run RuboCop on staged changes. RuboCop provides an option which automatically corrects certain pieces of code based on whether a particular cop is considered safe for autocorrect or not. We leveraged this in our git commit hook so that developers don't have to worry about manually fixing all the offenses. When the machine can do it, why not? --safe-autocorrect We used and npm packages to achieve this. husky lint-staged npm install --save-dev lint-staged husky Add following in package.json { : { : }, : { {app,spec}/**/*.rb bundle exec rubocop --safe-autocorrect git add devDependencies husky ^ lint-staged ^ "scripts" "precommit" "lint-staged" "lint-staged" ": [ " ", " " ] }, " ": { " ": " 0.13 .4 ", " ": " 3.6 .0 " } } This configuration makes sure that whenever a developer tries to commit code the offenses in that code which are deemed safe by RuboCop and the cops which support , are already fixed. After this our developers did not have to do anything extra apart from fixing offenses which are not considered safe by RuboCop manually. Auto Correct mechanism Work in progress continues Once the autocorrect git hook was in action, our list of offenses started coming down. We had to regenerate the .rubocop_todo.yml in between to get the accurate list of offenses. We decided against adding regeneration of .rubocop_todo.yml into Git hook as it was slow in our case. .rubocop_todo.yml can be regenerated from the same command that generated it. Happiness or Pain? After all of this, we were expecting the offensive code to go down and code quality to improve day by day without any bugs. But we faced few challenges. The autocorrect hook did some unexpected changes to our code in the process. We had a piece of code which has a method update_attributes. gem has a cop which changes calls to update_attributes with update. Though this change should happen only on Active Record models, the cop does not check whether the method is called on Active Record model or not. rubocop-rails ActiveRecord/Aliases So in our case, it changed the call to custom update_attributes to update but did not change the method definition. It remained update_attributes. This resulted into error. As we had enabled safe-autocorrect Git hook, the developers came to know of this only when the build failed in CI. We also faced issues with few other cops such as Rails/SaveBang and where they changed the code which was supposed to be not changed. Style/StringHashKeys In the end, we decided to remove the safe autocorrect hook and relied on manual fixing of the offensive code. Contributing back When we faced the issues related to some of the cops which were not really safe for the safe autocorrect option, we tried to fix them by submitting patches to RuboCop. https://github.com/rubocop-hq/rubocop-rails/pull/101 https://github.com/rubocop-hq/rubocop-rails/pull/98 https://github.com/rubocop-hq/rubocop/pull/7312 I will encourage everyone to do the same as it makes RuboCop better for everyone. In conclusion, I will list down what we learned from this adoption. Incremental adoption is key. Autocorrect can be tricky, depends on your test coverage and lot of other factors, so don't use it if you don't have to. Disable the cops that your team does not agree upon. We disabled cops such as , RSpec/AnyInstance RSpec/ExpectInHook, RSpec/AlignRightLetBrace with your findings, code fixes so that critical tools such as RuboCop become better for everyone. Contribute back to the community Want to know how we do Ruby and Rails at Memory.ai, subscribe to my newsletter here . Do you use RuboCop? Let me know how in the comments below or on Twitter at @_cha1tanya