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
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
# 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.
# config/routes.rb
resources :users do
resource :resignation, only: %i[create], module: 'users'
end
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
.
class Users::ApplicationController < ApplicationController
before_action :set_user
private
def set_user
@user = User.find(params[:user_id])
end
end
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.
# 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
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.
Top comments (0)