Form Object Pattern được sử dụng lúc nào?
- Khi cần phải thực hiện việc create/update multiple ActiveRecord models trên cùng một form
- Khi việc sử dụng
accepts_nested_attributes_fortrở nên phức tạp hoặc không muốn sử dụng - Tuân thủ nguyên tắc “single responsibility principle”
- Thin Model (việc chứa quá nhiều logic trong một model để phục vụ việc tương tác với form là không cần thiết gây nên sự phức tạp)
Form Object Pattern tương tự như Service Object. Nó là một lớp “layer” đặt giữa view và controller ( cũng như model) để thực hiện nhiệm vụ từ cả hai. Ví dụ:
- validate data
- CRUD
- notification
- create nested attributes
- …
Do đó việc sử dụng Form Object sẽ giữ cho C-M đơn giản.
Trong bài viết này sẽ giới thiệu gem Virtus ( Link ).
Vì gem sẽ hỗ trợ ta thực hiện form object pattern đơn giản và nhanh hơn (bản chất thì vẫn như vậy)
1.Bài toán
Có 2 model User 1-1 Grade. Trong đó Grade chứa thông tin grade(cấp bậc) name(tên lớp) của user.
Hãy tạo một chức năng register cho user bao gồm cả việc input thông tin grade.
2.Sử dụng Form Object
a.Add gemfile
#Gemfile
gem virtus
b.Tạo mới 1 class extend Virtus để định nghĩa các thuộc tính và validate
- Đầu tiên là khởi tạo
#app/forms/user_register_form.rb class UserRegisterForm include ActiveModel::Model include Virtus.model - Khai báo các attribute và type cần thiết.
Ở đây ta được phép khai báo thêm các attribute “ảo “ như
confirmđể thực hiệc việc kiểm tra confirm checkbox
attribute :firstname, String
attribute :lastname, String
attribute :grade, Integer
attribute :name, String
attribute :confirm, String
- Thực hiện add các validates
validates :firstname, presence: true
validates :lastname, presence: true
validates :name, presence: true
validates :grade, presence: true,
inclusion: {in: 1..12}
validates :confirm, acceptance: true
- Định nghĩa method
savetương tự như method save mặc định của ActiveRecord, để thực hiện những action mà chúng ta cần thực hiện
def save
return false unless valid?
create_register
true
end
private
def create_register
ActiveRecord::Base.transaction do
user = User.create!(firstname: firstname, lastname: lastname)
user.build_grade(name: name, grade: grade).save!
end
end
Lưu ý: Luôn sử dụng transaction khi thực hiện việc thay đổi nhiều model.
- Xử lý controller
class RegistersController < ApplicationController
def index
@users = User.all
end
def new
@user_register_form = UserRegisterForm.new
end
def create
@user_register_form = UserRegisterForm.new(user_register_form_params)
if @user_register_form.save
redirect_to registers_path, notice: "created"
else
render :new
end
end
private
# Using strong parameters
def user_register_form_params
params.require(:user_register_form).permit(:firstname, :lastname, :name, :grade, :confirm)
end
end
- Xử lý views
<%= form_for @user_register_form, url: registers_path do |f| %>
<%= f.label :firstname %>
<%= f.text_field :firstname %>
<%= f.label :lastname %>
<%= f.text_field :lastname %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :grade %>
<%= f.text_field :grade %>
<%= f.label :confirm %>
<%= f.check_box :confirm %>
<%= f.submit %>
<% end %>
Như vậy Form Object: UserRegisterForm chịu trách nhiệm nhận các input đầu vào từ form register và sau đó thực hiện confirm, validate và lưu data ở các model tương ứng.
Nếu nhiều form, ta sẽ tạo nhiều Form Object tương ứng để xử lý mà không có đá động gì tới model gốc .
Ngoài ra còn có gem Reform : Link
Bài viết tham khảo