【问题标题】:What's the right way to list all paper_trail versions including associations?列出所有 paper_trail 版本(包括关联)的正确方法是什么?
【发布时间】:2015-12-16 07:11:34
【问题描述】:

关于the paper_trail gem的问题。

仅当关联发生变化时,不会为主模型创建版本记录。那么,列出某个记录(包括其关联)的所有版本的正确方法是什么?

我应该查询这样的内容吗? (不好的一点是这个 SQL 查询可能很长而且性能很低。)

f = "(item_type = 'Place' AND item_id = ?) OR (item_type = 'PlaceName' AND item_id IN (?))"
PaperTrail::Version.where(f, @place.id, @place.names.map { |n| n.id }) 

或者我应该在仅更改关联时创建版本记录吗?我认为@DavidHam 尝试过同样的事情并问过a similar question,但还没有人回答。

【问题讨论】:

  • 要使用这种方法跟踪关联,当我们需要删除关联时,必须软删除它们。

标签: ruby-on-rails paper-trail-gem


【解决方案1】:

所以,我找到了一种方法来做到这一点,但它并不完全漂亮,并且每次更改关联时它都不会创建新版本。但是,它确实为您提供了一种按时间顺序检索版本的有效方法,因此您可以查看版本在关联更改之前/之后的样子。

首先,我检索给定模型 id 的关联版本的所有 id:

def associations_version_ids(item_id=nil)
  if !item_id.nil?
    ids = PaperTrail::VersionAssociation.where(foreign_key_id: item_id, foreign_key_name: 'item_id').select(:version_id)

    return ids
  else
    ids = PaperTrail::VersionAssociation.where(foreign_key_name: 'item_id').select(:version_id)

    return ids
  end
end

然后我使用此函数中的 VersionAssociation id 将所有版本放在一起。它将返回一个按时间顺序排列的 PaperTrail::Version 数组。因此,这些信息对于活动日志等很有用。通过这种方式拼凑一个版本及其关联非常简单:

def all_versions
  if !@item_id.nil?
    association_version_ids = associations_version_ids(@item_id)
    all_versions = PaperTrail::Version
                      .where("(item_type = ? AND item_id = ?) OR id IN (?)", 'Item', @item_id, association_version_ids)
                      .where("object_changes IS NOT NULL")
                      .order(created_at: :desc)

    return all_versions
  else
    assocation_ids = associations_version_ids
    all_versions = PaperTrail::Version
                      .where("item_type = ? OR id IN (?)", 'Item', association_ids)
                      .where("object_changes IS NOT NULL")
                      .order(created_at: :desc)

    return all_versions
  end
end

同样,这不是一个完美的答案,因为每次发生变化时都没有新版本,但它是可以管理的。

【讨论】:

  • 项目是主模型? item_id 什么时候会为零?
  • 是的项目是主要模型。所以 item_id 为 nil 的情况应该使用该模型的所有记录。老实说,该部分尚未使用/测试,因此如果您愿意,我可以在没有它的情况下编辑我的代码示例。
  • 我试过你的代码,但它错过了一些关联版本。
  • 我的主要模型是 Place,但它的一些关联的 foreign_key_name 不是 'place_id'。
  • 似乎多态关联使用不同的foreign_key_name。
【解决方案2】:

这更像是一种方法,而不是具体的答案,但这里就是这样。

就我而言,我需要一个版本历史记录,以便任何人更改Child 时,他们也会更改“父级”上的标志。但我需要一种方法来显示审计跟踪,以显示所有子项的初始值,并在任何人更改子项时为父项显示一条审计行。

所以,很简单,就像这样:

class Parent < ActiveRecord::Base
  has_paper_trail
  has_many :children
end

class Child < ActiveRecord::Base
  has_paper_trail
  belongs_to :parent
end

因此,每当Child 发生更改时,我们都需要在Parent 上创建一个版本。

首先,尝试如下更改Child

class Child < ActiveRecord::Base
  has_paper_trail
  belongs_to :parent, touch: true
end

每当Child 更改时,这应该(应该!尚未测试)在Parent 上创建一个时间戳。

然后,要获取:childrenParent 的每个版本中的状态,请在Child 的版本中搜索transaction_idParent 匹配的版本。

# loop through the parent versions
@parent.versions.each do |version|

  @parent.children.versions.each do |child|
    # Then loop through the children and get the version of the Child where the transaction_id matches the given Parent version
    child_version = child.versions.find_by transaction_id: version.transaction_id

    if child_version # it will only exist if this Child changed in this Parent's version
      # do stuff with the child's version
    end

这在我的情况下有效,希望这里的东西对你有用。

【讨论】:

  • 当你使用touch: true同时创建多个children时,如何防止为parent创建多个版本?
  • 最后一个代码块(@parent.versions.each ...)是列出版本?
  • 关于你的第一个问题,我从来没有弄清楚父母的多个版本的问题,但在我的情况下,当孩子改变时,总是有一个属性设置在父母身上,所以我不必担心,我放弃了touch: true. 虽然如果说,你用touch: true 改变了三个:children,但不是父级,在同一次抛出中,你正在做版本事务,它可能只会在父级上创建一个版本-我不知道它是否会为每个子级更改执行一个版本并为它们提供相同的transaction_id
  • 我认为如果你这样做,你不能对版本进行分页,对吗?
  • 我不知道,我还没有处理这个问题。
【解决方案3】:

[更新]

我找到了更好的方法。您需要更新事务内的关联才能使此代码正常工作。

class Place < ActiveRecord::Base
  has_paper_trail
  before_update :check_update

  def check_update
    return if changed_notably?

    tracking_has_many_associations = [ ... ]
    tracking_has_has_one_associations = [ ... ]

    tracking_has_many_associations.each do |a|
      send(a).each do |r|
        if r.send(:changed_notably?) || r.marked_for_destruction?
          self.updated_at = DateTime.now
          return
        end
      end
    end
    tracking_has_one_associations.each do |a|
      r = send(a)
      if r.send(:changed_notably?) || r.marked_for_destruction?
        self.updated_at = DateTime.now
        return
      end
    end
  end
end

class Version < PaperTrail::Version
  def associated_versions
    Version.where(transaction_id: transaction_id) if transaction_id
  end
end

[原答案]

这是迄今为止我发现的最好的方法。 (@JohnSchaum 的回答对我很有帮助,谢谢!)

开始之前,我已将polymorphic_type 列添加到versions 表中。

class AddPolymorphicTypeToVersions < ActiveRecord::Migration
  def change
    add_column :versions, :polymorphic_type, :string
  end
end

然后像这样设置模型:

# app/models/version.rb
class Version < PaperTrail::Version
  has_many :associations, class_name: 'PaperTrail::VersionAssociation'
end

# app/models/link.rb
class Link < ActiveRecord::Base
  has_paper_trail meta: { polymorphic_type: :linkable_type }
  belongs_to :linkable, polymorphic: true
end

# app/models/place.rb
class Place < ActiveRecord::Base
  has_paper_trail
  has_many :links, as: :linkable

  def all_versions
    f = '(item_type = "Place" AND item_id = ?) OR ' +
        '(foreign_key_name = "place_id" AND foreign_key_id = ?) OR ' +
        '(polymorphic_type = "Place" AND foreign_key_id = ?)'
    Version.includes(:associations).references(:associations).where(f, id, id, id)
  end
end

我们现在可以获取包含如下关联的版本:

@place.all_versions.order('created_at DESC')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-19
    • 1970-01-01
    • 2010-11-27
    • 2020-05-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多