Form object, giải pháp thay thế cho Active Record Nested Attributes
Xin chào các bạn Chắc hẳn những ai sử dụng Rails cũng đã đều quen thuộc với `accepts_nested_attributes_for` để xử lý Nested Attributes. Tuy nhiên, khi ứng dụng của bạn lớn, việc sử dụng accepts_nested_attributes_for khiến cho Model ngày càng trở nên rối, dần dần dẫn đến tình trạng "fat model, ...
Chắc hẳn những ai sử dụng Rails cũng đã đều quen thuộc với `accepts_nested_attributes_for` để xử lý Nested Attributes.
Tuy nhiên, khi ứng dụng của bạn lớn, việc sử dụng accepts_nested_attributes_for khiến cho Model ngày càng trở nên rối, dần dần dẫn đến tình trạng "fat model, skinny controller".
Bởi vậy hôm nay tôi sẽ trình bày một trong nhưng cách để giải quyết vấn đề này - Form Object
- Cảm ơn anh DieuNB đã suggest cho em kỹ thuật này (lay2) -
1. Form object là gì?
Form object là kỹ thuật tạo ra một class trung gian mang các attributes của những model cần tác động.
Tại Controller, thay vì khởi tạo đối tượng trực tiếp từ Model, ta sẽ gọi tới class trung gian này.
Việc sử dụng Form object giúp bạn giảm thiểu tình trạng "Fat model", đồng thời giúp code sáng sủa hơn, validates dễ dàng hơn.
Trên thực tế, nhiều lập trình viên có kinh nghiệm cũng phản đối việc sử dụng accepts_nested_attributes_for và dự đoán nó sẽ bị loại bỏ ở các bản Rails sau này (yaoming)
2. Sử dụng
Bài toán đặt ra:
- Khi người dùng signup, các thông tin cần nhập vào là username, email, address, github.
- username, email được lưu trong table Users. address, github được lưu trong table Profiles.
- User và Profile có quan hệ 1-1.
- Chỉ sử dụng 1 form.
Ta có 2 Model với quan hệ như sau:
class User < ActiveRecord::Base has_one :profile end
class Profile < ActiveRecord::Base belong_to :user end
Model User có trường: :username, :email
Model Profile có trường: :address, :github
Thay vì khai báo accepts_nested_attributes_for :profile trong model User, ta tạo ra 1 class mới đặt trong folder app/forms
class Signup include ActiveModel::Model attr_accessor :username, :email, :address, :github validates :address, presence: true validates :email, format: {with: /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/i, on: :create} validates :github, presence: true validates :username, presence: true def register if valid? create_user create_profile end end private def create_user hash = Hash.new hash[:username] = "#{username}" hash[:email] = "#{email}" @user ||= User.new hash @user.save! end def create_profile hash = Hash.new hash[:address] = "#{address}" hash[:github] = "#{github}" @profile ||= Profile.new hash @profile.user = @user @profile.save! end end
Ta include ActiveModel::Model để class có thể sử dụng các methods trong Model.
attr_accessor get, set cho các field sẽ sử dụng và xuất hiện trên form.
Method register dùng để create User và Profile tương ứng của nó. Method sẽ được gọi từ SignUpsController.
class SignupsController < ApplicationController def new @signup = Signup.new end def create @signup = Signup.new signup_params @signup.register end private def signup_params params.require(:signup).permit :username, :email, :address, :github end end
Tại `View` ta sử dụng `form_for` với object `@signup` được khai báo bên phía Controller như bình thường:
<%= form_for(@signup) do |f| %> <% if @signup.errors.any? %> <ul> <% @signup.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> <% end %> <%= f.label :username %> <%= f.text_field :username %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :address %> <%= f.text_field :address %> <%= f.label :github %> <%= f.text_field :github %> <%= f.submit %> <% end %>
Tham khảo
-
7 Patterns to Refactor Fat ActiveRecord Models:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Cảm ơn các bạn đã theo dõi (lay2)