+3

Form Object Pattern with reform gem

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ằ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 authenticationaccepting 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ệ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 newedit 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 ArtistSong 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. synsave 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí