Rails Associations has_one最新记录

时间:2013-04-05 00:32:38

标签: ruby-on-rails activerecord associations

我有以下型号:

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列,而不是尝试虚拟地或通过关联创建它?

5 个答案:

答案 0 :(得分:23)

要获得has_many的最后一个,你可能想要做类似@jvnill的事情,除了添加一个带有关联的排序的范围:

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

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

答案 1 :(得分:16)

您可以将其更改为关联,但通常情况下,在查询中使用时,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

答案 2 :(得分:2)

我理解你想要得到每个部分的最后一个修订版有parent_section_id = 1的部分;

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

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

我做了这个查询并使用了我的表格(希望我很好地命名了params,它与之前的Rails查询相同,但是我在更改中进行了更改);小心小组;包含使得它在大型数据集中最佳,抱歉我找不到与has_one建立关系的方法,但我会继续这样做,但也会重新考虑你在开头提到的字段。

答案 3 :(得分:0)

如@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 :(得分:0)

如果您的数据库支持 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)