首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >与Rails 6 ActiveRecord完全外接

与Rails 6 ActiveRecord完全外接
EN

Stack Overflow用户
提问于 2020-03-20 16:59:49
回答 1查看 1.4K关注 0票数 2

我正在用消息和通知对一个应用程序建模,如下(简化):

代码语言:javascript
运行
复制
# 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

型号:

代码语言:javascript
运行
复制
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中创建通知,也加载了所有消息。这是为了避免每次发布新消息时都需要在DB中创建尽可能多的通知行。只有当用户读取消息(存储read_at值)时,才会将一行添加到通知表中。

我成功地使用以下SQL实现了它:

代码语言:javascript
运行
复制
  # 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

例如,如果我有一个id=1消息,两个id=20和id=21用户,一个通知(id=30,message_id=1,recipient_id=20),对于用户21和消息1,我会得到read_at=nil的“虚拟”通知(因为用户还没有阅读)和相关的消息数据。

代码语言:javascript
运行
复制
> 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: ...>

然而:

instance);

  • Most非常冗长,每当向MessageNotification添加新属性时,代码都需要更新;例如,

  • --我不确定性能(我认为它很好,因为所有东西都已经加载到单个查询中了,我不认为对于存在N+1问题--重要的是,我真的更愿意使用has_many :notifications关联处理User,或者如果不可能的话,使用自定义notifications作用域。这是为了避免急切的加载,有一个更流畅的API,能够加入其他可能的关系,等等。或者至少我想改进语法,使之更像Rails。。

有什么想法吗?

EN

回答 1

Stack Overflow用户

发布于 2020-03-20 17:55:20

您可以获取属于用户的通知。然后检索没有通知目标用户的消息,并为它们构建一个通知。最后,将这两个集合组合起来。

代码语言:javascript
运行
复制
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.load + Message
      .where.not(id: notifications.pluck(:message_id)).pluck(:id)
      .map { |message_id| notifications.build(message_id: message_id) }
  end
end

遗憾的是,这仍然会产生数组结果。我不确定这是否可能有正常的联系。希望其他答案能证明我错了。

我真的想问你这是否产生了正确的结果?这个问题相当复杂,我不确定我对它的理解是否100%正确。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60778492

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档