Routing definition using only the actions generated by resources
Tags: ruby, rails
Published: 2024-03-11 18:33:38

## REST

The scaffold feature in Rails is a fantastic capability. Beyond just generating code to manipulate CRUD operations on a database table, it imparts crucial lessons in building applications. While scaffold automatically generates seven actions (index, show, new, edit, create, update, destroy) to handle CRUD operations, many applications find additional actions unnecessary and even believe they shouldn't be created.

Within REST, there is a concept called Uniform Interface. It standardizes the operations provided for a URI that represents a resource through HTTP methods. The following one-line declaration in Rails' config/routes.rb makes it easy to implement this concept.

```ruby
resources :users
```

## The Feature You're Building Isn't That Special

Determine your resources, define routing with resources for everything, and provide everything in your application through the seven actions it offers. Is it possible to build an application under these constraints? In most cases, I believe it is. You might say, "But my application is a bit unique and challenging!" However, I often think that the difficulty arises from design issues related to identifying the "resources."

For instance, let's consider a scenario in a certain application, contemplating operations like user withdrawal for registered users.

```ruby
# config/routes.rb
resources :users do
  post :resign
end
```
```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def resign
    # resign process.
  end
end
```

It is possible to achieve this by defining an action named `#resign` in the `UsersController`.

However, as you can see, the routing involves a non `resources` declaration, specifically `post :resign`. Ideally, it is desirable for the structure to consist only of actions generated by the scaffold, avoiding such deviations.

To address this, it might be beneficial to identify a separate noun (resource) such as "Resignation" and consider CRUD operations for it. Additionally, organizing within a namespace, like `users/`, in the current controller can enhance clarity.

```ruby
# config/routes.rb
resources :users do
  resource :resignation, only: %i[create], module: 'users'
end
```
```ruby
class Users::ResignationsController < ApplicationController
  def create
    # resign process.
  end
end
```

Considering the assumption that the user is uniquely identified and anticipating the potential growth of sub-resources in the future, it would be beneficial to define `Users::ApplicationController` as follows. This allows for the centralization of the assignment to `@user`.

```ruby
class Users::ApplicationController < ApplicationController
  before_action :set_user

  private

  def set_user
    @user = User.find(params[:user_id])
  end
end
```
```ruby
class Users::ResignationsController < Users::ApplicationController
  def creaste
    # resign process for @user
  end
end
```

For example, if you later want to add actions for listing and displaying details of "login history for that user," it would be as follows.

```ruby
# config/routes.rb
resources :users do
  resource :resignation, only: %i[create], module: 'users'
  resources :login_histories, only: %i[index show], module: 'users'

  # If you think it's redundant, use scope to summarize it.
  # scope module: 'users' do
  #   resource :resignation, only: %i[create]
  #   resources :login_histories, only: %i[index show]
  # end
end
```
```ruby
class Users::LoginHistoriesController < Users::ApplicationController
  before_action :set_login_history, only: %i[show]

  def index
    @login_histories = @users.login_histories
  end

  def show
  end

  private

  def set_login_history
    @login_history = @users.login_histories.find(params[:id])
  end
end
```

These are, of course, simple cases that can be prepared in advance. However, even in the face of unknown challenges that may arise in the future, gaining insights by identifying "nouns" rather than "verbs" for the events that occur there often surprisingly leads to successful solutions.