我正在使用以下消息和通知对应用程序进行建模(简化):
# db/schema.rb
create_table :messages do |t|
...
end
create_table :notifications do |t|
t.references :message, index: true, foreign_key: true, null: false
t.references :recipient, index: true, foreign_key: { to_table: :users }, null: false
t.datetime :read_at
...
end
型号:
class User < ApplicationRecord; end
class Message < ApplicationRecord; end
class Notification < ApplicationRecord
belongs_to :message
belongs_to :recipient, class_name: 'User'
accepts_nested_attributes_for :message
end
在此简化示例中:
我正在寻找最干净(最类似于Rails的)和性能最高的方式来加载用户的通知和相关消息,同时确保即使在数据库中尚未为此用户创建通知的情况下也加载所有消息。这是为了避免每次发布新消息时,在DB中创建与用户数量一样多的通知行。仅当用户阅读消息(存储read_at
值)时,才会在通知表中添加一行。
我设法使用此SQL来实现它:
# user.rb
def notifications
unsanitised_sql = <<-SQL
SELECT
:user_id AS recipient_id, m.id as message_id, n.read_at, n.created_at, n.updated_at,
m.text, ...
FROM (
SELECT *
FROM notifications
WHERE recipient_id = :user_id
) n
FULL OUTER JOIN messages m
ON (message_id = m.id)
SQL
ActiveRecord::Base
.connection
.select_all(ActiveRecord::Base.sanitize_sql [ unsanitised_sql, { user_id: id } ])
.map do |row|
Notification.new(
recipient_id: row['recipient_id'],
message_id: row['message_id'],
read_at: row['read_at'],
created_at: row['created_at'],
updated_at: row['updated_at'],
message_attributes: {
id: row['message_id'],
text: row['text'],
...
}
)
end
end
例如,对于用户21和消息1,我有一个ID为1的消息,两个ID为20和ID = 21的用户,一个通知为(ID = 30,message_id = 1,receiver_id = 20),收到带有read_at=nil
的“虚拟”通知(因为用户尚未阅读)和相关的消息数据👍
> User.find(21).notifications
=> [#<Notification:0x00007fb5c64df138 id: nil, message_id: 1, recipient_id: 21, read_at: nil, created_at: nil, updated_at: nil>]
> User.find(21).notifications.first.message
=> #<Message:0x00007fd57c52b5a8 id: 1, text: ...>
但是:
Message
或Notification
时,都需要更新代码;has_many :notifications
上的User
关联来实现相同的目标,或者,如果可能的话,使用自定义notifications
范围来实现相同的目标。这是为了避免急于加载,拥有更流畅的API,能够加入其他可能的关系等。或者至少我希望以某种方式将语法改进为更像Rails。有什么想法吗?
您可以获取属于用户的通知。然后检索没有针对目标用户的通知的消息,并为其建立通知。最后将两个集合合并。
class Message < ApplicationRecord
has_many :notifications
end
class Notification < ApplicationRecord
belongs_to :message
belongs_to :recipient, class_name: 'User'
end
class User < ApplicationRecord
has_many :notifications # you might need `inverse_of: :recipient`
def all_notifications
notifications = self.notifications.includes(:message).load
notifications + Message
.where.not(id: notifications.pluck(:message_id))
.map { |message| self.notifications.build(message: message) }
end
end
可悲的是,这仍然会产生数组结果。我不确定是否可以为此建立正常关联。希望其他答案能证明我错了。
我想问你这是否产生正确的结果?这个问题非常复杂,我不确定我的理解是否100%正确。