Form Object Pattern with reform gem
Bài đăng này đã không được cập nhật trong 6 năm
1. Giới thiệu
- Chắc hẳn các bạn đã đều quen thuộc với
accepts_nested_attributes
để xử lý các thuộc tính của bản ghi này thông qua bản ghi khác, tuy nhiên việc tạo form để xử lý attributes của 2 object thì vẫn có thể kiểm soát đc bằngaccepts_nested_attributes
, nhưng khi Form của bạn cần fai xử lý 3 objet hay thậm chí còn nhiều hơn thế nữa thì việc sử dụngaccepts_nested_attributes
trở nên rối rắm khó kiểm soát. Vì vậy phải đòi hỏi 1 cách xử lý khác tối ưu hơn, dễ kiểm soát hơn. Và cách mà mình đưa ra cho các bạn đó chính làForm Object
. - Ngoài ra, việc sử dụng Form Object còn giúp bạn giải quyết được vấn đề model bị mình to, mà người ta thường gọi là
fat model
. - Trong trường hợp này
fat model
của bạn gặp phái là khi mộtmodel
của bạn phải thực hiệnquá nhiều nhiệm vụ
, cả việcauthentication
vàaccepting attributes
cho các model khác, dẫn đến tình trạng nó quá lớn. Và khi đó, model sẽ vi phạm một quy tắc rất phổ biến đó làthe single responsibility principle
- Có 2 cách sử dụng Form object: 1 là thủ công , 2 là các bạn sử dụng gem hổ trợ.
- Bài viết này mình sẻ hướng dẫn các bạn sử dụng
gem reform
để áp dụng Form Object Pattern vào project.
2. Cài đặt
Thêm vào gemfile gem "reform"
Sau đó chạy bundle
Lưu ý: Reform 2.2 ko tự động load các Rails files (vd: ActiveModel::Validations
). và khắc phục bằng cách cài thêm gem "reform-rails
, hướng dẫn cài đặt tài đây.
3. Single model
3.1 Định nghĩa Forms
Form đc địch nghĩa trong các class riêng. Thông thường một phần của class này sẽ map với một model
class AlbumForm < Reform::Form
property :title
validates :title, presence: true
end
- Các fields được khai báo bằng từ khóa
property
. - Validate bằng cách sử dụng từ khóa
validates
, và hoạt động của validate tương tự như bạn bạn đã biết trong rails hoặc các Framework khác. Và việcvalidate
nàykhông còn nằm trong model
nữa.
3.2 Setup
Trong controller bạn muốn tạo Form. Khởi tạo instance của Form object cho action new
và edit
như sau:
class AlbumsController
def new
@form = AlbumForm.new(Album.new)
end
def edit
@form = AlbumForm.new(Album.find(1))
end
end
Tại đây Reform sẽ đọc các giá trị của property từ model trong thiết lập. Ở đây model là Album nên AlbumForm
sẽ goị album.title
để điền vào trường title
.
3.3 Render Form
Bây giờ, bạn có thể sử dụng @form
như bình thường để render ra view. có thể dùng các những method
thông dụng của ActionView
trong rails như: form_for
, simple_form
or formtastic
.
<%= form_for @form do |f| %>
Album title: <%= f.input :title %><br />
<%= f.submit %>
<% end %>
Đối với nested form và collection
bạn cũng có thể dễ dàng render và sử dụng với fields_for
. Tuy nhiên, lúc này nested form của bạn vẫn đc xử lý trong model. Để giải quyết thì mình sẽ giới thiệu ở phần bên dưới. Vì ở đây mình chỉ giới thiệu về single model để các bạn hiểu cơ chế. =))
3.4 Validation
Sau khi submit tiếp theo sẽ là validate các dữ liệu đầu vào.
def create
#=> params: {album: {title: "Album 1"}}
if @form.validate(params[:album])
Đầu tiên nó sẽ update các giá trị truyền vào từ form, sau đó sẽ chạy tất cả các validations mà mình đã cung cấp trong form.
Điều nay cho phép render lại Form sau khi validate với các giá trị sau khi submit. Tuy nhiên, các giá trị này chưa đc update vào model cho đến khi thực hiện save
.
3.5 Save
Việc dễ dàng nhất để lưu dữ liệu vào model là gọi method save
if @form.validate(params[:album])
@form.save
else
# handle validation errors.
end
3.6 Set giá trị Default
class AlbumForm < Reform::Form
property :price, default: 10
end
3.7 Save Form bằng cách thủ công
@form.save do |hash|
hash #=> {title: "Greatest Hits"}
Album.create(hash)
end
hoặc bạn cũng có thể thực hiện như sau
@form.save do |hash|
album = @form.model
album.update_attributes(hash[:album])
end
4. Nested model
4.1 Định nghĩa Form object
Đầu tiên ta có quan hệ giữa Album
với Artist
và Song
như sau:
class Album < ActiveRecord::Base
has_one :artist
has_many :songs
end
Và bạn muốn tạo nested form cho trường hợp này ta ta khai bao nested form như sau:
class AlbumForm < Reform::Form
property :title
validates :title, presence: true
property :artist do
property :full_name
validates :full_name, presence: true
end
collection :songs do
property :name
end
end
Bạn có thể sử dụng lại 1 form đã được khai bao trước bằng cách sử dụng từ khóa :form
ví dụ: ta có 1 ArtistForm đã khai báo trước đấy ta tái sử dụng lại như sau:
property :artist, form: ArtistForm
4.2 Nested Setup
Reform sẽ đóng gói các đối tượng được xác định lồng nhau theo hình thức riêng của họ. Điều này xảy ra tự động khi khởi tạo Form.
form = AlbumForm.new(album)
form.songs[0] #=> <SongForm model: <Song name:"Người Lạ ơi">>
form.songs[0].name #=> "Người Lạ ơi"
4.3 Nested Form rendering
Sử dụng như bình thường mình hay render form:
<%= form_for @form do |f| %>
Album title: <%= f.text_field :title %><br />
f.fields_for :artist do |a|
a.text_field :name
end
f.fields_for :song do |s|
s.text_field :name
end
<%= f.submit %>
<% end %>
4.4 Nested Processing
Validate sẽ gán các giá trị cho các nested form. syn
và save
lại tương tự như Form đối với single model.
Block của #save sẽ cung cấp cho bạn các dữ liệu sau.
@form.save do |nested|
nested #=> {title: "Hits Nhạc Việt",
# artist: {name: "Krik"},
# songs: [{title: "Người lạ ơi"},
# {title: "Ai ngờ ta còn thương"}]
# }
end
5. Kết luận
Trên đây mình đã giới thiệu qua cho các bạng cách xây dựng một Form object sử dụng gem reform và reform-rails. Hi vọng nó giúp cho các bạn để có thể làm việc với Form Object.
Tài liệu tham khảo
https://github.com/trailblazer/reform https://viblo.asia/p/form-objects-pattern-in-rails-OREkwLPKvlN
All rights reserved