DEV Community

Takashi SAKAGUCHI
Takashi SAKAGUCHI

Posted on

Routing definition using only the actions generated by resources

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.

resources :users
Enter fullscreen mode Exit fullscreen mode

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.

# config/routes.rb
resources :users do
  post :resign
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def resign
    # resign process.
  end
end
Enter fullscreen mode Exit fullscreen mode

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.

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

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.

class Users::ApplicationController < ApplicationController
  before_action :set_user

  private

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

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.

# 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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)