我的系统中有不同类型的用户。一种是,比方说,设计师:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
当用户注册时立即创建。因此,当用户填写sign_up表单时,会创建一个Devise User
以及此Designer
对象,并将user_id
设置为创建的新User
。如果我可以访问控制器的代码,这很容易。但是对于Devise,我无法访问此注册控制器。
注册时创建User
和Designer
的正确方法是什么?
答案 0 :(得分:9)
在最近的一个项目中,我使用form object pattern一步创建了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
关于表单对象(以及我的工作源)的优秀读物是this CodeClimate blog post on Refactoring。
总之,我更喜欢这种方法而不是使用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
模型中的这个结构与saucy一起建模,这是一个思想机器人的宝石。源不在Github AFAIK上,但可以从gem中提取它。我通过重塑它已经学到了很多东西。
答案 1 :(得分:2)
如果您不想更改注册控制器,一种方法是使用ActiveRecord回调
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end