繁体   English   中英

ActiveRecord has_one 其中关联 model 有两个 belongs_to 关联

[英]ActiveRecord has_one where associated model has two belongs_to associations

我有两个 ActiveRecord 模型,它们以这种方式相互关联:

class Address < ApplicationRecord
  has_one :user, class_name: User.name
end

class User < ApplicationRecord
  belongs_to :home_address, class_name: Address.name
  belongs_to :work_address, class_name: Address.name
end

User -> Address 关联工作正常:

home_address = Address.new
#=> <Address id:1>

work_address = Address.new
#=> <Address id:2>

user = User.create!(home_address: home_address, work_address: work_address)
#=> <User id:1, home_address_id: 1, work_address_id: 2>

user.home_address
#=> <Address id:1>

user.work_address
#=> <Address id:2>

我遇到的问题是让Addresshas_one正常工作。 起初我得到一个错误User#address_id does not exist ,这是有道理的,因为那不是外键字段的名称。 它可以home_address_idwork_address_id (我通过迁移添加了这些 FK)。 但我不确定如何让它知道要使用哪个地址,直到我了解到您可以将 scope 传递到has_one声明中:

class Address < ApplicationRecord
  has_one :user,
    ->(address) { where(home_address_id: address.id).or(where(work_address_id: address.id)) },
    class_name: User.name
end

但这会返回与之前相同的错误: Caused by PG::UndefinedColumn: ERROR: column users.address_id does not exist 这令人困惑,因为我在 scope 的任何地方都没有声明我正在查看address_id 我猜has_one隐含地有一个 foreign_key of:address_id,但我不知道如何设置它,因为从技术上讲有两个:home_address_id 和:work_address_id。

我觉得我离这里很近——我该如何修复这个 has_one 关联?

更新

我的直觉说这里的解决方案是只创建一个user方法来执行我要运行的查询,而不是声明has_one 如果has_one支持此功能,那就太好了,但如果不支持,我会回过头来。

class Address < ApplicationRecord
  def user
    User.find_by("home_address_id = ? OR work_address_id = ?", id, id)
  end
end

解决方案

感谢下面的@max。 我最终根据他的回答找到了解决方案。 我还使用了Enumerize gem,它将在Address model 中发挥作用。

class AddAddressTypeToAddresses < ActiveRecord::Migration[5.2]
  add_column :addresses, :address_type, :string
end
class User < ApplicationRecord
  has_many :addresses, class_name: Address.name, dependent: :destroy
  has_one :home_address, -> { Address.home.order(created_at: :desc) }, class_name: Address.name
  has_one :work_address, -> { Address.work.order(created_at: :desc) }, class_name: Address.name
end

class Address < ApplicationRecord
  extend Enumerize

  TYPE_HOME = 'home'
  TYPE_WORK = 'work'
  TYPES = [TYPE_HOME, TYPE_WORK]

  enumerize :address_type, in: TYPES, scope: :shallow
  # Shallow scope allows us to call Address.home or Address.work

  validates_uniqueness_of :address_type, scope: :user_id, if: -> { address_type == TYPE_WORK }
  # I only want work address to be unique per user - it's ok if they enter multiple home addresses, we'll just retrieve the latest one. Unique to my use case.
end

Rails 中的每个关联都可以只有一个外键,因为根据 SQL,您需要的是:

JOINS users 
ON users.home_address_id = addresses.id OR users.work_address_id = addresses.id

使用 lambda 为关联添加默认值 scope 在这里不起作用,因为 ActiveRecord 实际上不会让您胡闹它如何加入关联级别。 如果您考虑它生成了多少不同的查询以及该功能会导致的边缘情况的数量,这是完全可以理解的。

如果你真的想 go 在你的用户表上有两个不同的外键,你可以用单表 Inheritance 解决它:

class AddTypeToAddresses < ActiveRecord::Migration[6.1]
  def change
    add_column :addresses, :type, :string
  end
end
class User < ApplicationRecord
  belongs_to :home_address, class_name: 'HomeAddress'
  belongs_to :work_address, class_name: 'WorkAddress'
end
class HomeAddress < Address
  has_one :user, foreign_key: :home_address_id
end
class WorkAddress < Address
  has_one :user, foreign_key: :work_address_id
end

但我会将外键放在另一个表上并使用一对多关联:

class Address < ApplicationRecord
  belongs_to :user
end
class User < ApplicationRecord
  has_many :addresses
end

这使您可以根据需要添加任意数量的地址类型,而不必担心用户表。

如果你想将用户限制在一个家庭和一个工作地址,你会这样做:

class AddTypeToAddresses < ActiveRecord::Migration[6.1]
  def change
    add_column :addresses, :address_type, :integer, index: true, default: 0
    add_index :addresses, [:user_id, :address_type], unique: true
  end
end
class Address < ApplicationRecord
  belongs_to :user
  enum address_type: {
    home: 0,
    work: 1
  }
  validates_uniqueness_of :type, scope: :user_id
end    
class User < ApplicationRecord
  has_many :addresses
  has_one :home_address,
    -> { home },
    class_name: 'Address'
  has_one :work_address,
    -> { work },
    class_name: 'Address'
end

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM