【问题标题】:Rails Associations has_one Latest RecordRails Associations has_one 最新记录
【发布时间】:2013-04-05 00:32:37
【问题描述】:

我有以下型号:

class Section < ActiveRecord::Base
  belongs_to :page
  has_many :revisions, :class_name => 'SectionRevision', :foreign_key => 'section_id'
  has_many :references

  has_many :revisions, :class_name => 'SectionRevision', 
                       :foreign_key => 'section_id'

  delegate :position, to: :current_revision

  def current_revision
    self.revisions.order('created_at DESC').first
  end
end

其中current_revision 是最近创建的revision。是否可以将current_revision 转换为关联,以便我可以执行Section.where("current_revision.parent_section_id = '1'") 之类的查询?或者我应该将current_revision 列添加到我的数据库中,而不是尝试以虚拟方式或通过关联来创建它?

【问题讨论】:

    标签: ruby-on-rails activerecord associations


    【解决方案1】:

    要在 has_many 上获得最后一个,您需要执行类似于 @jvnill 的操作,除了向关联添加一个带有排序的范围:

    has_one :current_revision, -> { order created_at: :desc }, 
      class_name: 'SectionRevision', foreign_key: :section_id
    

    这将确保您从数据库中获得最新版本。

    【讨论】:

    • 我很困惑。 @jvnill 指出,当 has_one 关联加入时,订单部分被删除,而你的回答似乎没有改变。
    • 我的答案实际上是相同的,只是它采用的是旧的 rails 3.2 格式。应该将 order: 'created_at DESC' 移到开头,以便更容易看到。
    • 我想指出的是,当您尝试将其与不同的查询结合使用时,您必须注意注意事项。见第二个代码的例子。
    【解决方案2】:

    您可以将其更改为关联,但通常情况下,has_onebelongs_to 关联的排序在用于查询时总是被错误解释。在你的问题中,当你把它变成一个关联时,那将是

    has_one :current_revision, class_name: 'SectionRevision', foreign_key: :section_id, order: 'created_at DESC'
    

    这样做的问题是,当您尝试将其与其他查询结合使用时,它通常会给您错误的记录。

    >> record.current_revision
       # gives you the last revision
    >> record.joins(:current_revision).where(section_revisions: { id: 1 })
       # searches for the revision where the id is 1 ordered by created_at DESC
    

    所以我建议你添加一个current_revision_id

    【讨论】:

    • 美丽的正是我想要的!
    • 如果您对 section_revisions.section_id 有一个空约束,请确保不要使用 has_one 帮助程序 record.create_current_revision(或 record.build_current_revision),否则您将得到 Failed to remove the existing associated current_revision. The record failed to save after its foreign key was set to nil. 而始终使用 record.section_revisions.create ,这将是新的current_revision
    • 注意,对于 Rails 4+,您需要改为 has_one :current_revision, lambda{ order("created_at DESC") }, class_name: 'SectionRevision', foreign_key: :section_id
    • 我花了一些心思,但我找到了一种 100% 有效的方法。感谢您分享您的解决方案并说明其缺陷,它为我节省了大量时间和错误!
    【解决方案3】:

    正如@jvnill 所提到的,使用order 的解决方案在进行更大的查询时会停止工作,因为order 的范围是完整的查询,而不仅仅是关联。

    这里的解决方案需要准确的SQL:

      has_one  :current_revision, -> { where("NOT EXISTS (select 1 from section_revisions sr where sr.id > section_revisions.id and sr.section_id = section_revisions.section_id LIMIT 1)") }, class_name: 'SectionRevision', foreign_key: :section_id
    

    【讨论】:

      【解决方案4】:

      我了解您想要获取每个部分的最后修订版本具有 parent_section_id = 1 的部分;

      我有类似的情况,首先,这是 SQL(请认为类别为您的部分,帖子为修订版,user_id 为 parent_section_id -对不起,如果我没有将代码移动到您的需要,但我必须去):

      SELECT categories.*, MAX(posts.id) as M
      FROM `categories` 
      INNER JOIN `posts` 
      ON `posts`.`category_id` = `categories`.`id` 
      WHERE `posts`.`user_id` = 1
      GROUP BY posts.user_id
      having M = (select id from posts where category_id=categories.id order by id desc limit 1)
      

      这是 Rails 中的查询:

      Category.select("categories.*, MAX(posts.id) as M").joins(:posts).where(:posts => {:user_id => 1}).group("posts.user_id").having("M = (select id from posts where category_id=categories.id order by id desc limit 1)")
      

      这行得通,它很难看,我认为最好的方法是“剪切”查询,但是如果你有太多的部分,那么在循环它们时会出现问题;您也可以将此查询放入静态方法中,而且,您的第一个想法是,在您的sections 表中有一个 revision_id 将有助于优化查询,但会放弃规范化(有时需要),您必须在为该部分创建新修订时更新此字段(因此,如果您要在庞大的数据库中进行大量修订,那么如果您的服务器速度较慢,这可能是个坏主意......)

      更新 我回来了嘿嘿,我正在做一些测试,看看这个:

      def last_revision
          revisions.last
      end
      
      def self.last_sections_for(parent_section_id)
        ids = Section.includes(:revisions).collect{ |c| c.last_revision.id rescue nil }.delete_if {|x| x == nil}
      
        Section.select("sections.*, MAX(revisions.id) as M")
               .joins(:revisions)
               .where(:revisions => {:parent_section_id => parent_section_id})
               .group("revisions.parent_section_id")
               .having("M IN (?)", ids)
      end
      

      我做了这个查询并处理了我的表(希望我很好地命名了参数,它与之前的 Rails 查询相同,但我在优化时更改了查询);留意小组;包含使其在大型数据集中成为最佳选择,抱歉我找不到与 has_one 建立关系的方法,但我会继续这样做,但也要重新考虑您在开头提到的领域。

      【讨论】:

        【解决方案5】:

        如果你的数据库支持DISTINCT ON

        class Section < ApplicationRecord
          has_one :current_revision, -> { merge(SectionRevision.latest_by_section) }, class_name: "SectionRevision", inverse_of: :section
        end
        
        class SectionRevision < ApplicationRecord
          belongs_to: :section
          scope :latest_by_section, -> do
            query = arel_table
              .project(Arel.star)
              .distinct_on(arel_table[:section_id])
              .order(arel_table[:section_id].asc, arel_table[:created_at].desc)
            revisions = Arel::Nodes::TableAlias.new(
              Arel.sql(format("(%s)", query.to_sql)), arel_table.name
            )
            from(revisions)
          end
        end
        

        它适用于预加载

        Section.includes(:current_revision)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多