First commits in a Ruby on Rails app
Tags: ruby, rails, github, git
Published: 2024-01-17 10:49:21


I had the idea for this project over three months ago and created the Ruby on Rails repo around that time. 

I was unsure what form the product would have, so I focused first on setting up coding styles, static analysis checks, and some other defaults that I consider essential when starting a project. 

In this article, I will explore what I added and why I chose that specific tool, talking specifically from the perspective of a single-person side project. 


## What I wanted to achieve with my first commits

There is a kind of inertia to the code design: new code tends to be similar to existing code. 

Here is a better quote from Edmond Lau book called “The Effective Engineer” that shows the positive side of this inertia: 

> The code quality also self-propagates; new engineers model their own code based on the excellent code that they see, creating a positive feedback loop. 

When working alone on your project, you can replace “new engineer” with “you in the future,” the quote will still be relevant. 

My initial commits aimed to set up automated checks for code quality and security. I want to help my future self be fast and write quality code. 

Side projects developed while having a full-time job have a unique characteristic worth noting. The time dedicated to working on the side project is not continuous. For instance, you may work on it for 1-2 hours on Saturday, and the next opportunity to work on it may only arise a week later.

It is then essential to make the code quality built-in and use as much automation as possible. 

## First commits

My first commits were about: 

- adding Rubocop to correct and enforce coding styles automatically 
- check code quality with Rubycritic
- security audits with brakeman, bundler-audit and importmap audit
- enable Rails strict loading
- make Rails console run on sandbox mode in production

### Add Rubocop

I have a [Rubocop configuration](https://github.com/lucianghinda/personal-ruby-style) that I like to use in my side projects. 

The first commit sets up Rubocop and adds my configuration to the `.rubocop.yml` file.

#### Why add Rubocop

I have a lot of thoughts about what cop I want enabled and what cop disabled, and sometimes I change my mind about some of them, but for this project, consistency is more important than a specific cop. 

It is essential to have a consistent style while coding. I also like to have the editor auto-format my code, so I chose Rubocop because I want to use the new Ruby syntax, and thus, I can switch on/off rules that will impede this. 

#### What I added to the commit

**1. Rubocop gems**

I am using the following Rubocop gems: 

- [rubocop-rails](https://rubygems.org/gems/rubocop-rails) - “Automatic Rails code style checking tool”
- [rubocop-performance](https://rubygems.org/gems/rubocop-performance) - “A collection of RuboCop cops to check for performance optimizations in Ruby code”
- [rubocop-minitest](https://rubygems.org/gems/rubocop-minitest) - “Automatic Minitest code style checking tool”
- [rubocop-capybara](https://rubygems.org/gems/rubocop-capybara) - “Code style checking for Capybara test files (RSpec, Cucumber, Minitest)”
- [rubocop-rubycw](https://rubygems.org/gems/rubocop-rubycw) -  “Integrate RuboCop and ruby -cw. You can get Ruby's warning as a RuboCop offense by rubocop-rubycw”

**2. Apply Rubocop autocorrection to files generated by `Rails generate`**

I want to automatically run Rubocop with autocorrect on any `.rb` file generated by the Rails generate command. This will ensure that any gem that generates Ruby files or any I generate will have the style automatically corrected. 

I added the following code to the `config/application.rb` to do this: 

```ruby
config.generators.after_generate do |files|
  parsable_files = files.filter { |file| file.end_with?(".rb") }
  unless parsable_files.empty?
    system("bundle exec rubocop -A --fail-level=E #{parsable_files.shelljoin}", exception: true)
  end
end
```

I took this code from [Rubocop Rails Configuration tip](https://docs.rubocop.org/rubocop-rails/usage.html#rails-configuration-tip) and you can see it added to [the current Rails master](https://github.com/rails/rails/blob/main/railties/lib/rails/configuration.rb#L134)

And I made Rubocop run on Github CI with by adding the following content to the file under `.github/workflows/rubocop.yml`: 

```yml
name: Rubocop Linter

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
      
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.3
          bundler-cache: true

      - name: Install dependencies
        run: bundle install

      - name: Checking Rubocop Styles
        run: bundle exec rubocop
```

This will run Rubocop on the following two cases: 
- When code is pushed to the main branch.
- When a pull request is made to the main branch.

### Code Quality with Rubycritic

The third commit adds [Rubycritic](https://github.com/whitesmith/rubycritic) as a code quality static analysis. 

#### Why add Rubycritic

I added this because I want to ensure I create simple code. And while Rubycritic does not guarantee the code will be simple, it can help maintainability. I see maintainability as an essential metric in the context of a side project. 


#### The commit

I don’t want to run Rubycritic in CI mostly because I think about having little time and trying to be fast with feature releases, and sometimes refactoring to remove a code smell can take quite some time. 

But I do want to run this locally, so I just created a file under `bin/quality_check` with the following content: 

```bash
#!/bin/bash -e

bundle exec rubycritic 
```
 
Why a file and not run directly `rubycritic`? I’d like to add more tools like this here or configure Rubycritic in a specific way and having a bash script allows me this flexibility.

Rubycritic uses [reek](https://github.com/troessner/reek) under the hood so I added a reek config files at `.reek.yml` with the following content: 

```yml
---
detectors:
  IrresponsibleModule:
    enabled: false

  LongParameterList:
    enabled: true
    exclude: []
    max_params: 4
    overrides:
      initialize:
        max_params: 5

  DataClump:
    max_copies: 3
    min_clump_size: 3

### Excluding directories

exclude_paths:
  - test/
  - config/
```

I mostly disabled `IrresponsibleModule` because I don’t want to add a description to all classes/modules I create. 

I also allowed a maximum of 4 parameters for methods and 5 for the initializers. I also allowed a maximum of 3 methods in an object to have the same parameters, and the check happens only from 3 parameters up. I can rationalize this decision, but it is mainly based on my experience with extracting objects from a long list of parameters and finding a balance when to make this effort. 

### Security checks on CI

The fourth commit is concerned with adding security checks to the CI. 

#### Why add security checks

I want to run on CI a couple of security audits that I grouped under the name of `Static Analysis`: 

- [Brakeman](https://rubygems.org/gems/brakeman) - “Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis”
- [Bundle audit](https://rubygems.org/gems/bundle-audit) - “Patch-level verification for Bundler”
- [Importmap audit](https://github.com/rails/importmap-rails) - “checks the NPM registry for known security issues”

#### The commit

Here is the CI config for Github to run all these tools in `.github/workflows/static_analysis.yml`:

```yml
name: Static Analysis

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  static_analysis:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.3
          bundler-cache: true

      - name: Install Bundler
        run: gem install bundler

      - name: Install dependencies
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3

      - name: Run bundler-integrity check
        run: bundle exec bundler-integrity

      - name: Run Brakeman
        run: bundle exec brakeman

      - name: Run Bundle Audit
        run: bundle exec bundle-audit check --update

      - name: Run Importmap audit
        run: bundle exec bin/importmap audit
```

### Enable Rails strict loading

Here is what `strict_loading` does ([source](https://github.com/rails/rails/pull/37400)): 

> Add #strict to any record to prevent lazy loading of associations. strict will cascade down from the parent record to all the associations to help you catch any places where you may want to use preload instead of lazy loading.

#### Why enable strict loading

This is an excellent way to prevent N+1 and ensure that any needed association is included or preloaded. 

#### The commit

I enabled `strict_loading` in all environments: 

```ruby
# config/environments/development.rb

config.active_record.strict_loading_by_default = true
```

```ruby
# config/environments/test.rb

config.active_record.strict_loading_by_default = true
```

```ruby
# config/environments/production.rb

config.active_record.strict_loading_by_default = true
```

### Make Rails console run on sandbox mode in production

#### Why make console run in sandbox mode

It is a good practice to run the rails console in production in the sandbox box to ensure any change is intentional. 

I use `rails console` quite often in production, but I want to make sure that if I want to make any changes, that should be intentional and not accidental.

#### The commit

This is a [simple switch](https://github.com/rails/rails/pull/48984) that I can toggle in the Rails config for production: 

```ruby
# config/environments/production.rb

config.sandbox_by_default = true
```

If this is enabled, then to make changes, you have to run `rails console --no-sandbox`

## Conclusion

I made the first commits in this repository intentional about code quality and automatic checks. 

It helps with moving fast while keeping a good code quality. A lot can be done in this area, but I tried to add the minimum that would help me. 


---

### A note about eager loading code

A previous version of this article included a section about configuring CI to run `bin/rails zeitwerk:check` to check if the code loads correctly. 

[Xavier Noria](https://hashref.com) correctly pointed out that running this in CI is unnecessary. 

First, there is a config setting called [`config.eager_load`](https://guides.rubyonrails.org/v7.0/configuring.html#config-eager-load), and if you put that on `true` in your test environment and run any test that will eagerly load your entire application. This setting exists already in Rails 6.0 (and even before 6.0). 

Since Rails 7.0 the generated `config/environment/test.rb` includes the following line: 

```ruby
config.eager_load = ENV["CI"].present?
```

That means if you run your tests in Github CI (or similar CIs) that sets the `CI` environment variable, it will eagerly load your application. 

So there is no need to execute `rails zeitwerk:check` separately. Either set the `config.eager_load` to true on CI, or you already have that if you generated your app with Rails >= 7.0 and run your tests on CI. 

---

**Enjoyed this article?**

👐 Subscribe to my Ruby and Ruby on rails courses over email at [**learn.shortruby.com**](https://shortruby.com/)\- **effortless learning anytime, anywhere**

👉 Join my [**Short Ruby News**](https://shortruby.com/) **newsletter** for weekly Ruby updates from the community and visit [**rubyandrails.info**](https://shortruby.com/)**, a directory with learning content about Ruby.**

🤝 Let's connect on [**Ruby.social**](https://shortruby.com/) **or** [**Linkedin**](https://linkedin.com/in/lucianghinda) **or** [**Twitter**](https://x.com/lucianghinda) **where I post mainly about Ruby and Rails.**

🎥 Follow me on [**my YouTube channel**](https://www.youtube.com/@shortruby) for short videos about Ruby