在最近的一个项目中,我使用表单对象模式一步创建了 Devise 用户和公司。这涉及绕过 Devise 的 RegistrationsController 并创建您自己的 SignupsController。
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
@signup = Signup.new
end
def create
@signup = Signup.new(params[:signup])
if @signup.save
sign_in @signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
引用的注册模型被定义为一个表单对象。
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless @user.valid?
delegate_errors_for_account unless @account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
@user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
@account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, @user.errors[:name].first) if @user.errors[:name].present?
errors.add(:email, @user.errors[:email].first) if @user.errors[:email].present?
errors.add(:password, @user.errors[:password].first) if @user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, @account.errors[:name].first) if @account.errors[:name].present?
end
def persist!
@user.save!
@account.save!
create_admin_membership
end
def create_admin_membership
@membership = Membership.create! do |membership|
membership.user = @user
membership.account = @account
membership.admin = true
end
end
end
关于表单对象(以及我的工作的源代码)的优秀读物是关于重构的 CodeClimate 博客文章。
总而言之,我更喜欢这种方法而不是使用accepts_nested_attributes_for
,尽管可能有更好的方法。如果你找到一个,请告诉我!
===
编辑:添加了参考模型及其关联以便更好地理解。
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
模型中的这种结构与thinkbot的宝石 saucy 一起建模。源不在 Github AFAIK 上,但可以从 gem 中提取。我通过改造它学到了很多东西。