在 Rails 上使用 build() 添加额外的模型关系

Adding an additional model relationships using build() on Rails

目前我有以下四种型号:

users <-- agency_memberships --> agencies
               |
               |
          agency_roles
       

agency_memberships 是一个联接 table,agency_roles 是一个小型查找 table,角色为:

ID    NAME
-----------
1     admin
2     editor
...

在添加AgencyRole模型之前,创建用户时,如果参数create_agency = true,那么这足以创建一个新的Agency(沿着连接table AgencyMembership).

# user_controller.rb

def create
  @user = User.new(user_params)
  @user.agencies.build(name: "Demo Agency") if params[:user][:create_agency]

  if @user.save!
    ...
end

但是,现在我需要在保存之前添加一个有效的AgencyRole

是否可以使用 .build() 这样做或者 Rails 这样做的最佳做法是什么?

现在,我在保存之前手动创建所有关系,这有效但不够紧凑:

def create
  @user = User.new(user_params)

  if (params[:user][:create_agency])

    agency_name = params[:user][:agency_name]
    agency_role = AgencyRole.find_by(name: 'admin')
    
    @agency_membership = AgencyMembership.new(agency_role: @agency_role)
    @agency = Agency.new(name: agency_name)
    
    @agency_membership.agency = @agency
    @agency_membership.user = @user
    @agency_membership.agency_role = agency_role
    
    @user.agency_memberships << @agency_membership    
  end

  if @user.save!
    ...
end

编辑:我的模型关系如下:

class AgencyMembership < ApplicationRecord
  belongs_to :agency
  belongs_to :user
  belongs_to :agency_role
end

class AgencyRole < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: false }, presence: true
end

class Agency < ApplicationRecord
  has_many :agency_memberships
  has_many :projects
  has_many :users, through: :agency_memberships
end

class User < ApplicationRecord
  has_many :agency_memberships, inverse_of: :user
  has_many :agencies, through: :agency_memberships
end

你可以把它封装起来和controller分开,保留thin controller fat model,另外你可以用autosave来自动保存关联。

class AgencyMembership < ApplicationRecord
  belongs_to :agency_role, autosave: true
end

class Agency < ApplicationRecord
  has_one :agency_membership, autosave: true
end

module BuildAgency
  def build_with_role(attributes = {}, &block)
    agency_role_name = attributes.delete(:role)

    agency = build(attributes, &block) # association: user - membership - agency
    
    # add role    
    agency_role = AgencyRole.find_by(name: agency_role_name)
    agency.agency_membership.agency_role = agency_role # autosave

    agency
  end
end

class User < ApplicationRecord
  has_many :agencies, autosave: true, :extend => BuildAgency

  def build_agency(attributes)
   new_agency = agencies.build_with_role(attributes)
   # ...
   new_agency
  end
end

# controller
def create
  if (params[:user][:create_agency])
    @user.build_agency(name: params[:user][:agency_name], role: params[:user][:agency_role])
  end
  if @user.save! # it'll save agencies since we set `autosave`
end