【问题标题】:Setting up a mutual belongs_to in Ruby on Rails在 Ruby on Rails 中设置相互的 belongs_to
【发布时间】:2010-02-03 16:39:22
【问题描述】:

我正在创建一个维基。每篇文章有_many Revisions,一篇文章属于_单个current_revision。因此,在数据库中,Articles 对 Revision 的 id 有一个引用,并且每个 Revisions 对它们所属的 Article 都有一个引用。在我继续之前,这似乎是一种理智的做事方式吗?我觉得这很不正统,但合乎逻辑,我不确定其他人在类似情况下是如何设置的。

问题是这种相互的belongs_to 关系在创建模型时似乎真的让Rails 崩溃了。当我第一次创建一篇文章时,我还想创建一个初始修订版来配合它。

我添加了一个 before_create 方法并做了类似的事情:

initial_revision = self.revisions.build
self.current_revision = initial_revision

但这会导致保存时堆栈溢出,因为 Rails 显然会尝试在循环中首先保存文章,所以它有一个 article_id 粘贴在修订版中,然后首先保存修订版,所以它有一个 current_revision_id 到贴在文章里。

当我分解事物并且不同时创建它们(但仍在事务中)时,创建的第一个不会获得其参考集。 例如:

initial_revision = Revisions.create
self.current_revision = initial_revision
initial_revision.article = self

会留下一个空 article_id 的修订,因为它错过了保存。

我想我也可以通过调用 after_create 方法来解决这个问题,只是用更新和保存来初始化变量,但这变成了一个巨大的混乱,我觉得在 Rails 中这通常意味着我做错事。

任何人都可以提供帮助,还是我坚持创建一个保存更改的 after_create 小方法?

【问题讨论】:

    标签: ruby-on-rails wiki belongs-to


    【解决方案1】:

    我最近也有类似的问题。您只需要声明一种关联方式。是否可以在没有修订的情况下创建您的文章,然后将修订添加到现有文章中?

    或者你能从文章指向不向后指向的修订吗?如果这不可能,那么您需要将 Revision 声明为belongs_to :article,以及文章:has_many :revisionshas_one :revision, :conditions => { ... }。并在修订模型中添加标志“主要修订”或按日期获取最新修订。

    这样你就不会提供循环依赖,所以应该更容易。

    编辑:
    这就是我测试它并使其工作的方式:

    class Article < ActiveRecord::Base
      has_many :revisions
      has_one :current_revision, :class_name => "Revision", :conditions => { :tag => "current" }
    
      before_validation do |article|
        # add current revision to list of all revisions, and mark first revision as current unless one is marked as current
        article.current_revision = article.revisions.first unless article.current_revision.present?
        article.revisions << article.current_revision if article.current_revision.present? and not article.revisions.member?(article.current_revision)
      end
    
      after_save do |article|
        article.current_revision.mark_as_current if article.current_revision.present?
      end
    end
    
    class Revision < ActiveRecord::Base
      belongs_to :article
    
      def mark_as_current
        Revision.update_all("tag = ''", :article_id => self.article_id)
        self.tag = "current"
        save!
      end
    
    end
    

    这就是它现在的工作方式(从脚本/控制台转储):

    $ ./script/console
    Loading development environment (Rails 2.3.5)
    >> a1 = Article.new :name => "A1"
    >> a1.revisions.build :number => 1
    >> a1.save
    >> a1.reload
    >> a1.revisions
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 1  | 1          | 1      | current | 2010-02-03 19:10:37 UTC | 2010-02-03 19:10:37 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    >> a1.current_revision
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 1  | 1          | 1      | current | 2010-02-03 19:10:37 UTC | 2010-02-03 19:10:37 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    >> a1r2 = a1.revisions.build :number => 2
    +------------+--------+-----+------------+------------+
    | article_id | number | tag | created_at | updated_at |
    +------------+--------+-----+------------+------------+
    | 1          | 2      |     |            |            |
    +------------+--------+-----+------------+------------+
    >> a1r2.mark_as_current
    >> a1.revisions
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 1  | 1          | 1      | current | 2010-02-03 19:10:37 UTC | 2010-02-03 19:10:37 UTC |
    | 2  | 1          | 2      | current | 2010-02-03 19:11:44 UTC | 2010-02-03 19:11:44 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    >> a1.revisions.reload
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 1  | 1          | 1      |         | 2010-02-03 19:10:37 UTC | 2010-02-03 19:10:37 UTC |
    | 2  | 1          | 2      | current | 2010-02-03 19:11:44 UTC | 2010-02-03 19:11:44 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    >> a1.current_revision
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 1  | 1          | 1      | current | 2010-02-03 19:10:37 UTC | 2010-02-03 19:10:37 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    >> a1.reload
    >> a1.current_revision
    +----+------------+--------+---------+-------------------------+-------------------------+
    | id | article_id | number | tag     | created_at              | updated_at              |
    +----+------------+--------+---------+-------------------------+-------------------------+
    | 2  | 1          | 2      | current | 2010-02-03 19:11:44 UTC | 2010-02-03 19:11:44 UTC |
    +----+------------+--------+---------+-------------------------+-------------------------+
    

    在重新加载文章上的修订集合之前注意两个修订标记为当前的问题。当您将其中一个修订标记为当前时,您需要重新加载整个文章对象(如果您想使用 current_revision 字段)或仅重新加载修订集合。

    您可能应该只将current_revision 视为只读指针。如果您尝试为其分配另一个修订版,那么您将丢失文章指出的当前修订版(Rails 将删除旧的引用对象,因为 has_one)。

    【讨论】:

    • 在没有修订的情况下创建我的文章,然后使用 after_create 将修订添加到现有文章是我尝试的另一种方法,但是除非我在 after_create 中再次对文章调用保存,否则没有办法更新我的 current_revision_id,所以它是相同的丑陋代码但镜像。
    • 不幸的是,我确实需要双向指向。不过,修订模型中主要修订的标志是个好主意,似乎可以解决我的问题。你认为这在概念上比使用 after_create 方法来设置/保存变量更好吗?我刚刚尝试了 after_create 方法,它确实有效,只是很难看。 after_create 方法的优点是,在创建并初始化之后,我不需要再次修改 Revisions。
    • @williamjones 在 Ruby 中你仍然有双向指向,这就是为什么你有 has_one/manybelongs_to。您在数据库中只有单向指向。我试着在几分钟内用更新来回答你其余的 cmets
    【解决方案2】:

    修订只是文章的一个版本,对吗? Model Versioning 上有一个很棒的 Railscast,使用 vestal_versions gem 应该可以解决您的问题。

    【讨论】:

    • 不完全是,Revision 存储了有关文章的所有信息,而文章只是一个不变的中心枢纽,通过它可以找到修订。不过,感谢您提供有关 gem 的提示,我会研究一下,看看它是否比我目前的方法更好。
    【解决方案3】:

    我认为最好的方法是让每个修订都属于一篇文章。而不是属于修订(当前)的每篇文章的周期性关联。使用 has_one 关系将文章链接到最新版本。

    class Revision < ActiveRecord::Base
      belongs_to :article
      ...
    end
    
    class Article < ActiveRecord::Base
      has_many :revisions
      has_one :current_revision, :order => "version_number DESC"
      ...
    end
    

    但是,如果发生回滚,您将增加回滚到的修订版本号。

    另外...如果a.version_number &gt; b.version_number 且仅当a.id &gt; b.id 时,您可以消除version_number 字段并仅对id 进行排序。这意味着回滚将导致克隆记录的 id 高于上一个版本。

    【讨论】:

      【解决方案4】:

      我在自己的应用中遇到了同样的问题,虽然我的结构略有不同,但我终于找到了解决方案。

      在我的应用中,我有更多类似的东西:

      class Author < ActiveRecord::Base
        has_many :articles
        has_many :revisions
      end
      
      class Article < ActiveRecord::Base
        has_many :revisions
        belongs_to :author
      end
      
      class Revision < ActiveRecord::Base
        belongs_to :article
        belongs_to :author
      end
      

      所以我有一个 3 模型循环。

      就我而言,我想一次保存整个层次结构(从新的)。我发现我可以通过创建一个新作者来做到这一点,然后像往常一样将文章添加到作者,但是当我想创建修订时,我会这样做(在 Author 类中):

      def add_new_revision(@author)
        article.revisions = article.revisions.push(Revision.new(:author => @author))
      end
      

      (注意这里的@author 也没有保存)

      不知何故,这是可行的。我注意到在日志中,activerecord 在作者和文章已保存之后插入修订(就像使用 after_create 处理程序一样)。我不确定为什么这与构建的处理方式不同,但它似乎有效(尽管如果它不适用于其他任何人,我不会感到惊讶!)

      无论如何,我希望这会有所帮助! (对不起,您发布问题后这么久!)

      【讨论】:

        猜你喜欢
        • 2011-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 2017-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多