【问题标题】:Porting complicated has_many relationships to Rails >4.1 (without finder_sql)将复杂的 has_many 关系移植到 Rails >4.1(没有 finder_sql)
【发布时间】:2015-01-18 12:40:30
【问题描述】:

我正在将 Rails 应用程序移植到 Rails 4.2。这个 Rails 应用程序在关联中包含一些相当复杂的手动 SQL 代码 - 部分原因是 DB 优化(例如,子选择而不是 JOIN),部分原因是在编写时没有可行的替代方案(Rails 3.0),部分原因肯定是缺乏知识(我希望,至少 - 这很容易解决)。

示例:InternalMessage 类。消息可以在用户之间发送(InternalMessage 的收件人和消息的“删除”存储在 InternalMessagesRecipients 中,因为可以有多个),并且可以阅读、回复、转发和删除它们。关联如下所示:

class User < AR::Base
  has_many :internal_messages,
      :finder_sql => "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " +
          ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
          ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}',
      :counter_sql => 'SELECT count(DISTINCT(internal_messages.id)) FROM internal_messages ' +
          ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
          ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}'
  # ...
end

关键部分是最后的“OR”子句 - 通过这个关联,我想同时获得接收和发送的消息,它们分别与用户表连接:

  has_many :sent_messages, -> { where(:sender_deleted_at => nil) }, :class_name => 'InternalMessage', :foreign_key => 'sender_id' #, :include => :sender
  has_many :internal_messages_recipients, :foreign_key => 'recipient_id'
  has_many :rcvd_messages, :through => :internal_messages_recipients,  :source => :internal_message, :class_name => 'InternalMessage'

因为一个 InternalMessage 可能有多个收件人(也可以发送给发件人本人)。

问:如何将此 finder_sql 移植到兼容 Rails 4.2 的 has_many 定义?

【问题讨论】:

    标签: ruby-on-rails activerecord model-associations finder-sql


    【解决方案1】:

    更新

    我不久前了解到这是没有意义的。 has_many 关系必须至少在一个方向上具有单射连接,因此 SQL 子句中的“OR”没有意义。 CREATE 操作应该如何决定要满足哪个条件来创建新记录?这种关系是只读的,因此它不是has_many 关系。

    在这种情况下,一个简单的类方法(或范围)将是正确的答案,而不是has_many。要连接来自多个查询的结果,请使用类似

    def internal_messages
      InternalMessage.where( id: sent_message_ids + received_message_ids)
    end
    

    保持生成的对象可链接(即@user.internal_messages.by_date 等)

    【讨论】:

    • 当然,如果你想记忆结果,这是次要的,特别是它们与reload一起工作。有没有办法让rails在没有外键的情况下维持这些关系?
    【解决方案2】:

    将包含 SQL 字符串的 proc 作为作用域传递。

    has_many :internal_messages, -> { proc { "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " +
          ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
          ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' } }
    

    【讨论】:

    • 好的,谢谢。 counter_sql 怎么样?不再需要了?在 Rails 3 中,由于 DISTINCT,我在不使用 counter_sql 时会遇到 SQL 错误。
    • 我自己从来没有真正做到这一点,但this stackoverflow answer 说你可以在给 has_many 的块中定义自己的计数方法。所以我想你可以在块中做类似def count proxy_association.owner.class.count_by_sql("YOUR SQL HERE"); end 的事情。
    • 更新:Rails 4.2.5.1, ruby​​ 2.3.1p112 has_many :talks, -&gt;{ proc { "SOME SQL"}} NoMethodError: undefined method 'except' for #
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-24
    • 1970-01-01
    • 1970-01-01
    • 2015-02-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多