ruby-on-rails - ActiveRecord has_one 其中关联模型有两个 belongs_to 关联
问题描述
我有两个以这种方式相互关联的 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
用户 - > 地址关联工作正常:
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>
我遇到的问题是让Address
'shas_one
正常工作。起初我得到一个错误 that User#address_id does not exist
,这是有道理的,因为这不是外键字段的名称。要么是要么 (我通过迁移添加了这些 FK)。但是我不确定如何让它知道要使用哪个地址,直到我了解到您可以将范围传递给声明:home_address_id
work_address_id
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
. 这令人困惑,因为在该范围内我没有声明我正在查看address_id
. 我猜has_one
隐含地有一个foreign_key :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!我最终根据他的回答提出了解决方案。我还使用了gem,它将在模型Enumerize
中发挥作用。Address
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 为关联添加默认范围将不起作用,因为 ActiveRecord 实际上并没有让您在关联级别上如何加入。如果您考虑它生成多少不同的查询以及该功能会导致的边缘情况的数量,这是完全可以理解的。
如果你真的想在你的用户表上有两个不同的外键的兔子洞,你可以用单表继承来解决它:
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
推荐阅读
- node.js - 文件夹挂载应用程序上的 Express 中间件
- php - 40 个请求后的 xmlhttp-readystate=0
- jquery - 如何使用 jQuery enhsplitter 在垂直面板中设置水平子面板高度?
- java - JGiT拉挂
- python - Exchangelib:将电子邮件从收件箱移动到文件夹
- laravel - 来自 API 的清漆和动态内容
- sql-server - 交换 SSRS 的数据视图
- react-native - 相机始终在 React Native Router Flux 场景中工作
- constructor - 析构函数调用究竟是如何进行的
- ruby - 使用 .rspec 文件输出 html 报告