Question

Je crée un wiki. Chaque article has_many révisions, et un article belongs_to un seul current_revision. Ainsi, dans la base de données, les articles ont une seule référence à l'identifiant d'une révision, et les révisions ont chacun une seule référence à l'article auquel ils appartiennent. Avant de poursuivre, cela semble comme une façon saine de faire les choses? Il me semble assez peu orthodoxe, mais logique, et je ne suis pas sûr de savoir comment les autres dans des situations similaires mis les choses.

Le problème est que ce type de relation mutuelle belongs_to semble vraiment jeter Rails lorsque la création des modèles. Quand je crée d'abord un article, je voudrais aussi créer une révision initiale pour aller avec elle.

J'ai ajouté une méthode de before_create et fait quelque chose comme:

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

mais cela provoquerait un débordement de pile lors de l'enregistrement, comme Rails tente apparemment dans une boucle d'abord enregistrer l'article, il a un article_id à coller dans la révision, puis enregistrez d'abord la révision, il a un current_revision_id à tenir à l'article.

Quand je casse les choses, et ne les crée pas simultanément (mais toujours dans une transaction), le premier ne soit pas créé son jeu référence. Par exemple:

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

laisserait la révision avec une article_id nulle comme il a manqué la sauvegarde.

Je pense que je pourrais contourner ce problème en appelant une méthode de after_create ainsi, pour initialiser la variable avec une mise à jour et enregistrer, mais cela se transforme en un gigantesque gâchis, et je me sens comme dans Rails cela signifie généralement que je suis faire le mal-headedly quelque chose.

Quelqu'un peut-il aider, ou suis-je coincé créer un peu la méthode after_create qui sauve dans le changement?

Était-ce utile?

La solution

I a le même problème récemment. Vous devez déclarer qu'une seule façon d'association. Peut votre article créé sans révision, puis révision ajouté à l'article existant?

Ou pouvez-vous indiquer l'article à la révision qui ne pointe pas en arrière? Si cela ne devrait pas être possible, alors vous devez déclarer la révision comme belongs_to :article, et à l'article :has_many :revisions et has_one :revision, :conditions => { ... }. Et d'ajouter « révision principale » au modèle de révision drapeau ou d'obtenir la dernière révision par date.

De cette façon, vous ne fournissez pas les dépendances cycliques, il devrait être plus facile.

Edit: Voici comment je l'ai testé et le faire fonctionner:

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

Et voilà comment cela fonctionne maintenant (vidage de script / console):

$ ./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 |
+----+------------+--------+---------+-------------------------+-------------------------+

Surveillez problème avec deux révisions marquées comme cours avant reload collection de révisions sur l'article. Lorsque vous marquez l'une des révisions que le courant, alors vous devez vous recharger tout l'objet de l'article (si vous voulez utiliser le champ de current_revision) ou la seule collection de révision.

Et vous devriez probablement traiter current_revision seulement comme un pointeur en lecture seule. Si vous essayez d'affecter une autre révision, alors vous perdrez la révision précédente qui a été souligné par l'article en cours (Rails enlever l'ancien objet référencé, en raison de has_one).

Autres conseils

Une révision est juste une version d'un article, non? Il y a un grand Railscast sur Modèle Versioning en utilisant la gemme vestal_versions qui devrait résoudre votre problème.

Je pense que la meilleure façon de le faire est d'avoir chaque révision appartiennent à un article. Au lieu de l'association cyclique de chaque article appartenant à une révision (actuelle). Utilisez une relation has_one pour lier un article à la dernière révision.

class Revision < ActiveRecord::Base
  belongs_to :article
  ...
end

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

Cependant, en cas d'annulation, vous aurez augmenter le numéro de version de la révision annulée à.

Et aussi ... vous pouvez éliminer le champ numéro_version et il suffit de commander sur id si a.version_number > b.version_number et seulement si a.id > b.id. Ce qui signifie que rollbacks résulteront dans les dossiers clonés avec ids plus élevés que la dernière version.

J'ai eu le même problème dans ma propre application, et bien que ma structure est légèrement différente, je l'ai finalement trouvé une solution.

Dans mon application, j'ai quelque chose comme ceci:

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

J'ai une boucle 3-modèle à la place.

Dans mon cas, je veux sauver toute la hiérarchie (de nouveau) à la fois. J'ai trouvé que je peux le faire en créant un nouvel auteur, puis en ajoutant les articles à l'auteur comme normal, mais quand je veux créer les révisions, je le fais comme ça (à partir de la classe Auteur):

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

(Notez que ici @author n'a pas encore été enregistré soit)

D'une certaine façon cela fonctionne. J'ai remarqué que dans les journaux, activerecord insère la révision après l'auteur et l'article ont été enregistré (tout comme l'utilisation du gestionnaire after_create). Je ne sais pas pourquoi cela est traité différemment à faire construire, mais il semble fonctionner (bien que je ne serais pas surpris si elle ne fonctionne pas pour quelqu'un d'autre!)

Quoi qu'il en soit, je l'espère qui aide! (Désolé, il est si longtemps après que vous avez posté la question!)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top