在Ruby on Rails中设置一个mutual belongs_to

时间:2010-02-03 16:39:23

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

我正在创建一个wiki。每篇文章都有多个版本,一篇文章属于一个current_revision。因此,在数据库中,Article只有一个对Revision的id的引用,而Revisions每个引用它们所属的Article。在我继续之前,这看起来像是一种理智的做事方式吗?它让我感到相当不正统,但又符合逻辑,而且我不确定处于类似情况的其他人是如何设定的。

麻烦的是,在创建模型时,这种类型的相互belongs_to关系似乎真的会抛出Rails。当我第一次创建文章时,我还想创建一个初始修订版本。

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

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

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

当我分解并且不同时创建它们(但仍然在事务中)时,创建的第一个没有得到它的引用集。 例如:

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

会因为错过了保存而将版本保留为null article_id。

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

任何人都可以提供帮助,或者我是否因为创建了一个保存更改的post_create方法而停滞不前?

4 个答案:

答案 0 :(得分:4)

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

或者你可以从文章指向不指向的修订版吗?如果不可能,那么您需要将修订版声明为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视为只读指针。如果您尝试为其分配另一个修订版,那么您将松开以前的修订版本,该修订版本被文章指定为当前版本(由于has_one,Rails将删除旧的引用对象)。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

我认为获得它的最佳方法是让每个修订版属于一篇文章。而不是属于修订版(当前)的每个条款的周期性关联。使用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

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

此外...您可以删除version_number字段,只需在a.version_number > b.version_number上输入ID,并且仅在a.id > b.id时排序。这意味着回滚将导致克隆记录的ID比上一版本高。

答案 3 :(得分:1)

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

在我的应用中,我有更多类似的内容:

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模型循环。

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

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

(请注意,此处@author尚未保存)

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

无论如何,我希望有所帮助! (对不起,你发布这个问题已经很久了!)