CRUD stands for Create, Read, Update, Delete. That's four methods (or five if you distinguish between viewing one record and viewing all records). In Rails, it seems like the canonical way to handle CRUD involves seven methods. For instance, making routes for an Order object using the resources :orders
shorthand generates the following seven routes:
Here's the source of my confusion. What's the point of having separate actions/routes for new
/create
and edit
/update
? What is the benefit of having separate actions for viewing the page vs. creating the record in the database? I understand how it is done in Rails, e.g.:
class OrdersController < ApplicationController
def new
@order = Order.new
render
end
def create
@order = Order.new(order_params)
if @order.save
redirect_to @order, notice: 'Successfully created an order.'
else
render :new
end
end
end
Before working with Rails, I used Yii (PHP), which had a built-in CRUD generator that generated code like this (translated to Rails):
class OrdersController < ApplicationController
def create
if request.method == "POST"
@order = Order.new(order_params)
if @order.save
redirct_to @order, notice: 'Successfully created an order.'
end
else
@order = Order.new
end
render
end
end
The reason I prefer this pattern is because it avoids having to render a template for a different controller action. In the first code example, the user goes to orders/new
and posts to orders/create
. If validation fails, the user is still stuck at orders/create
but viewing the template for orders/new
. This might confuse the user, and it also seems to defeat the whole idea of having separate actions for viewing a page vs. creating a record in the database. And if you redirect to orders/new
rather than using render :new
then you lose all your validation error messages.
I find myself subconsciously slipping back to the Yii way in my Rails code. Can anyone explain why the standard way is advantageous? Are there any problems I am going to run into if I deviate from the canonical Rails pattern?
StackOverflow is warning me that the question is subjective and likely to be closed, so let me clarify. I am not trying to debate Rails vs. Yii or determine the best theoretical way to organize CRUD code. I want to know if there is anything about Rails that will break if I deviate from the canonical Rails way of handling CRUD.
The simple explanation is:
because that's how God and DHH intended it.
The Rails CRUD conventions are quite pragmatic and allow you to avoid many pitfalls related to browser caching and security.
Lets take one example:
# config/routes.rb
resources :users, only: [:new, :create]
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
sign_in(@user)
redirect_to root_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
Here we have
two separate routes GET /users/new
and POST /users
.
The first route is idempotent - it should look the same for any visitor and can be cached. The second is not - it should show the result of creating or attempting to create a resource.
When a user visits /users/new
we POST the form to a different URI. That avoids history issues in the client.
We also render the form in the same request cycle if the input is invalid. This avoids the security issues that would arise if we tried to pass the form data back in redirect to /users/new
and lets us return the semantically correct response code instead of a redirect.
It also ensures that our application is stateless in the restful sense since the previous action does not influence what we see if we were to visit /users/new
.
On an architecture level, it allows you to leverage convention over configuration. You can just do:
def new
@user = User.new
end
And it will render views/users/new.html.erb
because Rails can derive that the new action should render the new template. Having each controller action perform one single task is far better from a design and testing standpoint as it eliminates the need to test two separate code paths in the same method.