提问人:João Menighin 提问时间:10/26/2023 更新时间:10/27/2023 访问量:110
基于列值的自定义has_one
Custom has_one based on column value
问:
刚接触 Rails 的人想知道是否可以用 ActiveRecords 来模拟这种关系:
我有以下模型(简化):
User:
- name
- email
Client:
- name
- address
然后我有一个模型,它与这两个表相关,但有一个类型列:attendee
| id | join_type | join_id |
|----|-----------|---------|
| 1 | user | 1 |
| 2 | client | 1 |
有没有办法使用关系对活动记录进行建模?has_one
像这样:
class Attendee < ApplicationRecord
# OBVIOUSLY DOESNT WORK
has_one :user, ->() { where(join_type: 'user') }, :class_name => 'User'
has_one :client, ->() { where(join_type: 'client') }, :class_name => 'Client'
end
所以它会生成正确的左连接,所以?
谢谢
答:
我的方法是定义一个模型和一个模型,两者都继承自与会者(= 单表继承,STI)。因此,与会者将需要一个列,该列将在创建子类时自动填充。UserAttendee
ClientAttendee
type
# app/models/client_attendee.rb
class ClientAttendee < Attendee
has_one :client
end
# app/models/user_attendee.rb
class UserAttendee < Attendee
has_one :user
end
您仍然可以根据需要使用该类。
您在与会者表中显示一个字段,这对于has_one关系不是必需的,只有belongs_to关系是必需的。 并将有列 user_attendee_id 和 client_attendee_id。Attendee
join_id
User
Client
class User < ActiveRecord::Base
belongs_to :user_attendee
end
class Client < ActiveRecord::Base
belongs_to :client_attendee
end
使用此结构创建与会者表
class CreateAttendees < ActiveRecord::Migration[7.0]
def change
create_table :attendees do |t|
t.timestamps
t.string :linked_object_type
t.bigint :linked_object_id
end
# optinal
add_index :attendees, %i[linked_object_type linked_object_id]
end
end
# app/models/client_attendee.rb
class Attendee < ApplicationRecord
belongs_to :linked_object, polymorphic: true, foreign_type: :linked_object_type
end
然后,您可以像这样从Attendee实例访问用户/客户端
attendee = Attendee.first
linked_object = attendee.linked_object # Depends on linked object type it will return the relative model instance
如果要从“用户/客户”访问与会者,可以按如下方式定义他们
# frozen_string_literal: true
# This model is used to store user data
class User < ApplicationRecord
has_many :attendees, as: :linked_object, linked_object_type: 'User' # You can scope it yourself, and define has_one depends on your design.
end
评论
linked_object_type
client
ClientUser
user
SystemUser
t.references :linked_object, polymorphic: true
linked_object_type: 'User'
ActiveRecord
max
欢迎来到 Rails。我想以这种方式提出一个更简单的解决方案:
class CreateAttendees < ActiveRecord::Migration[7.0]
def change
create_table :attendees do |t|
t.timestamps
t.integer :user_id
t.integer :client_id
end
#add Indices and Foreign Keys as you wish
#add_index...
#add_foreign_key...
end
end
这样,你就可以使用你最喜欢的数据库系统的所有数据完整性功能,并简化你的rails代码,如下所示:
class Attendee < ApplicationRecord
belongs_to :user, optional: true
belongs_to :client, optional: true
#if you like you can add a getter method:
def linked_object
return user || client
end
end
class User < ActiveRecord::Base
has_one :attendee # or has_many - as your data model requires
# You can even add some data integrity features in rails using this:
# has_one :attendee, dependent: destroy
end
class Client < ActiveRecord::Base
has_one :attendee
#has_one :attendee, dependent: destroy
end
这样,代码就更易于阅读和维护。你会很高兴没有无处可去的与会者记录。
首先,你正在成为 和 的可怕命名造成的经典混乱的受害者。has_one
belongs_to
belongs_to
表示此模型表具有引用另一个表(或同一表)的列。这才是你真正想要的。
has_one
表示另一端有一列引用此表。有点像,但它生成不同的方法并附加到查询上。如果它被命名,它将为我们节省很多 greif .has_many
LIMIT 1
referenced_by
我认为您可能正在寻找的是通过具有多态关联的连接表的间接关联。
class Client
has_many :attendences
has_many :meetings,
through: :attendences,
as: :attendee
end
class User
has_many :attendences
has_many :meetings,
through: :attendences,
as: :attendee
end
# I took the liberty of making the naming less confusing
class Attendence
belongs_to :meeting
belongs_to :attendee,
polymorpic: true
end
# Just an example - this class could be whatever you want
class Meeting
has_many :attendences
has_many :attendees, through: :attendences
has_many :users, through: :attendences
has_many :clients, through: :attendences
end
其工作方式是,出勤表同时具有attendee_id列和attendee_type列。后者存储关联项的类名。
您可以在迁移中同时创建这两个列,方法是将选项传递给:polymorphic
add_reference
class CreateAttendences < ActiveRecord::Migration[7.0]
def change
create_table :attendences do |t|
t.references :attendee, null: false, polymorphic: true
t.references :meeting, null: false, foreign_key: true
t.timestamps
end
end
end
如果这实际上是一个好的设计,那么这是值得商榷的,因为您不能使用外键约束来维护数据库级别的引用完整性。
通过简单地创建两个联接表可以实现相同的目标,即使通过 ActiveRecord 也无法真正将其视为从最后开始的单个关联。meetings
评论