Form Object Pattern with reform gem
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ằng accepts_nested_attributes, nhưng khi Form của bạn cần fai xử lý 3 objet hay ...
- 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ằng accepts_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ụng accepts_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ột model của bạn phải thực hiện quá nhiều nhiệm vụ, cả việc authentication 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.
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.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ệc validate này khô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.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
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.
https://github.com/trailblazer/reform https://viblo.asia/p/form-objects-pattern-in-rails-OREkwLPKvlN