Настройка взаимных принадлежностей в Ruby on Rails - PullRequest
2 голосов
/ 03 февраля 2010

Я создаю вики.Каждая статья имеет множество ревизий, а статья принадлежит одному текущему обзору.Таким образом, в базе данных Статьи имеют одну ссылку на идентификатор редакции, а каждая редакция имеет одну ссылку на статью, к которой принадлежит.Прежде чем я продолжу, кажется ли это нормальным способом делать вещи?Это кажется мне довольно неортодоксальным, но логичным, и я не уверен, как другие в подобных ситуациях настраивают вещи.

Проблема в том, что этот тип взаимных отношений принадлежат, кажется, действительно отбрасывает Rails при созданиимоделей.Когда я впервые создаю статью, я хотел бы также создать начальную ревизию, чтобы идти с ней.

Я добавил метод before_create и сделал что-то вроде:

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

, но этовызвать переполнение стека при сохранении, так как Rails, по-видимому, пытается в цикле сначала сохранить Article, поэтому у него есть article_id, который нужно вставить в Revision, а затем сначала сохранить Revision, так что у него есть current_revision_id, который нужно вставить в Article.

Когда я разбиваю вещи и не создаю их одновременно (но все еще в транзакции), первая созданная не получает свой набор ссылок.Например:

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

оставил бы ревизию с нулевым article_id, поскольку он пропустил сохранение.

Я думаю, что я мог бы обойти это, вызвав также метод after_create, просто для инициализациипеременная с обновлением и сохранением, но это превращается в гигантский беспорядок, и я чувствую, что в Rails это обычно означает, что я делаю что-то неуместно.

Может кто-нибудь помочь, или я застрял, создаваянебольшой метод after_create, который сохраняет изменения?

Ответы [ 4 ]

4 голосов
/ 03 февраля 2010

У меня недавно была похожая проблема. Вам нужно заявить только об одном способе объединения. Может ли ваша статья быть создана без ревизии, а затем добавлена ​​ревизия к существующей статье?

Или вы можете указать из статьи в редакцию, которая не указывает назад? Если это невозможно, вам нужно объявить Revision как belongs_to :article, а Article :has_many :revisions и has_one :revision, :conditions => { ... }. И добавьте флаг «главная ревизия» в модель ревизии или получите последнюю ревизию по дате.

Таким образом, вы не предоставляете циклические зависимости, поэтому должно быть проще.

Edit:
Вот как я это проверил и заставил работать:

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).

2 голосов
/ 03 февраля 2010

Я думаю, что лучший способ иметь это - это чтобы каждая ревизия принадлежала статье. Вместо циклического объединения каждой статьи, принадлежащей редакции (текущей). Используйте отношение 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 и просто заказать по id, если a.version_number > b.version_number и только если a.id > b.id. Это означает, что откат приведет к клонированным записям с более высокими идентификаторами, чем в последней версии.

2 голосов
/ 03 февраля 2010

Редакция - это просто версия статьи, верно?Существует замечательный Railscast для Model Versioning с использованием гема vestal_versions, который должен решить вашу проблему.

1 голос
/ 30 сентября 2010

У меня была такая же проблема в моем собственном приложении, и хотя моя структура немного отличается, я наконец-то нашел решение.

В моем приложении есть что-то похожее на это:

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). Я не уверен, почему к сборке это относится иначе, но, похоже, это работает (хотя я не удивлюсь, если это не сработает ни у кого другого!)

В любом случае, надеюсь, это поможет! (Извините, это так долго после того, как вы отправили вопрос!)

...