Rails关系混乱

时间:2015-07-25 16:45:29

标签: ruby-on-rails relationship models

我试图在Rails设置中建立一些关系,并且对如何使用我配置的那些感到困惑。

我的情况是这样的: 我有一个名为Coaster的模型。我希望每个过山车能够拥有0个或更多版本。我希望能够从它的实例中找到所有版本的过山车,也可以反过来。

我的模特和关系:

coaster.rb:

has_many :incarnations
has_many :coaster_versions,
  through: :incarnations

incarnation.rb:

belongs_to :coaster
belongs_to :coaster_version,
           class_name: "Coaster",
           foreign_key: "coaster_id"

Incarnations的数据库架构:

create_table "incarnations", force: :cascade do |t|
  t.integer "coaster_id"
  t.integer "is_version_of_id"
  t.boolean "is_latest"
  t.integer "version_order"
end

以及从我的CSV数据文件导入Coasters时发生的代码:

 # Versions
 # Now determine if this is a new version of existing coaster or not
 if Coaster.where(order_ridden: row[:order_ridden]).count == 1

   # Create Coaster Version that equals itself.
   coaster.incarnations.create!({is_version_of_id: coaster.id, is_latest: true})
 else

   # Set original and/or previous incarnations of this coaster to not be latest
   Coaster.where(order_ridden: row[:order_ridden]).each do |c|
     c.incarnations.each do |i|
       i.update({is_latest: false})
     end
   end

   # Add new incarnation by finding original version
   original_coaster = Coaster.unscoped.where(order_ridden: row[:order_ridden]).order(version_number: :asc).first
   coaster.incarnations.create!({is_version_of_id: original_coaster.id, is_latest: true})

现在我的所有数据库表都已填写,但我不确定如何确保一切正常运行。

例如,我有两个过山车(A和B),B是A的版本。当我得到A并要求计算它coaster_versions时,我只返回1作为结果?当然我应该得到2还是正确的?

在同一行中,如果我得到B并且呼叫coaster_versions我也会返回1。

我只需要确保我真的得到了正确的结果。

任何评论都会受到高度赞赏,因为我已经多年来一直在研究这个问题并且没有走得太远。

只是因为任何人都会回复告诉我要看版本宝石。我最初走这条路线很棒,但问题是在我的情况下,过山车和过山车的版本都是彼此重要的,我不能做Coaster.all来得到所有过山车他们是否是版本与否。同一条线上的其他问题也出现了。

由于

1 个答案:

答案 0 :(得分:1)

首先,欢迎来到精彩的历史追踪世界!正如您所发现的那样,跟踪关系数据库中数据的变化实际上并不容易。虽然有绝对的宝石可以跟踪历史记录以进行审计(例如auditable),但听起来您希望您的历史记录仍然是一等公民。所以,让我首先分析您当前方法的问题,然后我提出一个更简单的解决方案,可以让您的生活更轻松。

没有特别的顺序,以下是您当前系统的一些难点:

  1. 必须维护is_latest列,并且存在不同步的风险。你可能不会在测试中看到这一点,但在生产中,大规模,这是一个非常有效的风险。

  2. 您的incarnations表创建了一个具有多个子版本的one-master-version版本,除了(类似于is_latest)版本的排序由{控制}之外,这很好。 {1}}列再次需要维护并且存在错误风险。您的导入脚本目前似乎无法设置它。

  3. version_order关系使得很难说B是A的版本;你可以通过一些更多的关系解决这个问题,但这也会使你的代码变得更复杂。

  4. 复杂性。很难跟踪历史记录的跟踪方式,正如您所发现的那样,很难管理插入新版本的细节(A和B都应该同意他们有2个版本,对吗?因为他们是同一个过山车?)

  5. 现在,我认为您的数据模型在技术上仍然有效 - 我认为您遇到的问题是您的脚本问题。但是,使用更简单的数据模型,您的脚本可以变得更简单,因此不容易出错。我只能使用一个表来实现这一目标:

    incarnations

    create_table "coasters", force: :cascade do |t| t.string "name" t.integer "original_version_id" t.datetime "superseded_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false end original_version_id表的作用相同,用于将版本链接回原始记录。 incarnations列可用作superseded_at检查和订购版本的方式(尽管如下,我只是按is_latest排序以简化)。有了这个结构,这就是我的Coaster类:

    id

    这使得添加新记录变得简单:

    class Coaster < ActiveRecord::Base
      belongs_to :original_version, class_name: "Coaster"
    
      scope :latest,   -> { where(superseded_at: nil) }
      scope :original, -> { where('original_version_id = id') }
    
      # Create a new record, linked properly in history.
      def self.insert(attrs)
        # Find the current latest version.
        if previous_coaster = Coaster.latest.find_by(name: attrs[:name])
          # At the same time, create the new version (linked back to the original version)
          # and deprecate the current latest version. A transaction ensures either both
          # happen, or neither do.
          transaction do
            create!(attrs.merge(original_version_id: previous_coaster.original_version_id))
            previous_coaster.update_column(:superseded_at, Time.now)
          end
        else
          # Create the first version. Set its original version id to itself, to simplify
          # our logic.
          transaction do
            new_record = create!(attrs)
            new_record.update_column(:original_version_id, new_record.id)
          end
        end
      end
    
      # Retrieve all records linked to the same original version. This will return the
      # same result for any of the versions.
      def versions
        self.class.where(original_version_id: original_version_id)
      end
    
      # Return our version as an ordinal, e.g. 1 for the very first version.
      def version
        versions.where(['id <= ?', id]).count
      end
    end
    

    当然,它仍然是复杂的代码,使得更简单。我的方法肯定不是唯一的,也不一定是最好的。希望你能学到一些东西!