Refactoring Guide for Rails Beginners
Tags: rails, refactoring
Published: 2022-12-26 00:26:46

# Overview

A guide for Rails beginners (those with less than two to three years of Rails experience) who don't know where to start refactoring.

This article summarizes basic refactoring techniques and the flow of it that I, as a freelance SE, have been involved in some projects, which can be used in almost any system.

Note that it doesn't be written too much about javascript.

# Procedure

Refactoring everything at the same time is a hard process. If you do not have its priority, you can refactor in the following order.

1. Organize Fat Controller
2. Organize Fat Model (& Service classes)
3. Organize around View

## Organize Fat Controller
### Dare to aim for Fat Model at the beginning.

By collecting logic in a Model, the Model scale can be grasped, and it is easy to consider policies, such as whether to use Gems or Service classes.

### What to do
- Public methods should be Actions only.
- Reduce the method length
  - Reduce nesting (for example, use `return if`)
  - Split the methods

If it is difficult to move code to Model and refactor at the same time, do not think, "The code that should be in the Controller...", move almost whole code to Model and then move it back to Controller as needed.

This **"Only the minimum necessary code should be in the Controller, and most of the remaining code should be in the Model "** is the first goal.

## Organize Fat Model (& Service classes)

- Split the methods and reduce nesting as with the Controller.
- Define `scope`, `association`, and `conditional(scoped) association` for easier debugging.
- Move value check to Validations.
- Separate code for each feature.

### How to separate code for each feature

1. Separate it as a Module in the file.
2. Separate it as a Module in an external file.
3. Separate it as a Service class.

In the following, consider where to move methods when there are more Model methods like this.
```
class User < ApplicationRecord
  def method1
  end

  def method2
  end

  def method3
  end
end
```

#### Separate it as a Module in the file
Similar functions can be blocked by `Module#concerning` to whole file looks better.

EXAMPLE. From the above code, method2 and method3 are `concerning` as follows.

```
class User < ApplicationRecord
  def method1
  end

  concerning :AnyModule do
    def method2
    end

    def method3
    end
  end
end
```

In this case, `user.method2` works in the same way as a normal instance method.

It is also good to use this temporarily when the refactoring policy has not been decided or when further modification is planned in the near future.
However, `concerning` may seem foreign to those who don't use it, so you might want to explain to them what that is the first time you use it. Incidentally, `concerning` has no direct relationship to the `concerns/` directory.

##### CANNOT reduce the lines in Model by this?
In response to this question, here is the conclusion I actually came to after researching it and asking around.

- **Even if the file is large, it is not much of a problem as long as its contents are organized.**
- If possible, it is better to divide the table into smaller tables to reduce the role of each table (functions of the Model).

#### Separate it as a Module in an external file
As an example, if you want to seperate the code of `user.rb`, seperate as `users/any_module.rb`.
It is not recommended to use `concerns/` directory just because it is an external module.
Only common code used multiple Models can be placed in `concerns/`.

It is the simplest compared to other ways, but it is important to keep a good balance because splitting up even a small code results in so many files.

However, I don't think I see this approach very often these days. The reason may be that it is difficult to manage when the including Modules more increases. If a Module does not need to be always included in a Model, it may be better to separate it from the Model as a Service class than to have it in a separate file.

```
class User < ApplicationRecord
  include AnyModule

  def method1
  end
end

module AnyModule
  def method2
  end

  def method3
  end
end
```

Same in this case, `user.method2` works as a instance method.

#### Separate it as a Service class

There are no common rules for this, so the team will need to decide on implementation policy. It would also be a good idea to share the OSS Git repository and use its policy.

On the other hand, it can be hell if each member starts creating their own Service class.
So, considering the difficulty of setting rules and continuously operating them, it is often better to include code in Model rather than using a Service class, depending on the team's situation.

**Whether or not to use the Service classes is just an option.**

I would like to go into more detail about the implementation rules, but I think that the method of managing Service classes in a good way is beyond the scope of this article that for the begginer, so that's all.

## Organize around View
- Separate logic into helpers.
- Partialize common parts.
- Use `form_with` for model update. `form_tag`, `form_for` are deprecated.
- Organize javascript
  - Separate js files for each page instead of writing code directly in the view.
  - Processes that can be written on the server side should be done on the server side.
- Organize css
  - Use `class` instead of using `style` directly.
  - Use `js-` prefix for js-only classes.

# Others

## Need specs when refactoring

If it does not have a RSpec yet, it would be good to create one at the time and confirm that the results are the same before and after refactoring. Needless to say, refactoring assumes that the specifications are clear. If the specifications are not clear for a legacy system, the requirements should be redefined.

## It's more important that the whole structure is organized than details.
The larger methods are the more it will be wanted to point out the details in the code review. If the method is compact or has a spec, it will not need to worry about the details.

## Don't over refactor
Do not spend too much time on areas that are infrequently used or where it will not be customized in the future.
Also, don't let writing clean code become an end in itself. YAGNI.

Conversely, if there are plans to customize in the near future, it is better to refactor a little more firmly.

When you are a code reviewer, you should keep this in mind and avoid excessive refactoring requests or make suitable refactoring suggestions.

## Make a Pull Request smaller
Refactoring tends to result in huge changes, making it difficult to grasp what changed. It also places a heavy burden on the code reviewer. By dividing the work into smaller pieces instead of cramming it into one PR, you will be less likely to get stuck in your own work.

If you want to work at once, you can make changes in a temporary branch and later incorporate them into a new branch with the following commands.

- `git cherry-pick` Import commits from another branch.
- `git diff [branch name] --name only` and then
  `git checkout [branch name] -- [filename]`
  to get the changed files.
- `git stash` Temporarily save your changes.

## To make search functions on the admin
Use `ransack` gem.
I recommend it because it can fast coding.

## To reduce copy-and-paste
- When creating a new file, generate suitable files by rails generator commands or scaffold.
- Use the snippet tool.

## To improve code quality
Use `rubocop` gem.
Automatic modification with `rubocop -a` or `rubocop -A` (unsafe) will naturally help you to write clean code.

## To make compact methods
Use `rails console` for development.
Easier to create compact methods than to proceed using breakpoints with `byebug`, etc. It also makes it harder to become a `Fat controller`.

## Don't be too quick to install third-party tools
There are many great Gem and javascript add-ons out there, but it doesn't mean that you should just throw them in.

The more you move away from the Rails standard, the more you have to deal with operation rules, maintenance costs, update risks, learning costs for newcomers, etc.
It is important to understand the advantages and disadvantages and decide that there is no problem.