[英]Have conditional belongs_to and has_one associations for ActiveRecord?
[英]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>
我遇到的問題是讓Address
的has_one
正常工作。 起初我得到一個錯誤User#address_id does not exist
,這是有道理的,因為那不是外鍵字段的名稱。 它可以是home_address_id
或work_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.