问题
How would you model the references and citations to publications (articles, books, chapters, etc...)?
A publication can be an article, book or a chapter and it has many references to other publications and other publications refer to it (call these citations)
I need to be able to list the relationships among the publications: The references in a publication and the citations from other publications to this publication
My initial understanding is that this would be a polymorphic relationship to handle the different types of publications and that it would require a bidirectionalself join.
My stab at it
Publication
belongs_to :writing, :polymorphic =>true
has_and_belongs_to_many :references
:class_name => "Publication"
:join_table => 'reference_citation'
:foreign_key => 'reference_id'
:foreign_key => 'citation_id'
Book, Chapter, Article all have:
has_many :publications :as =>writing
I find this a bit confusing so any suggestions that would help clarify it would be great. Even object and field naming suggestions.
[I asked a less clear version of this question here.]
I also probably need to use has many through because I will need the ability to destroy the relationship
回答1:
Here's a solution using a self-referential relationship using single table inheritance. Use these commands to create the app:
$ rails myproject
$ cd myproject
$ script/generate model publication type:string name:string
$ script/generate model citation publication_id:integer reference_id:integer
The setup the relationships this way:
class Publication < ActiveRecord::Base
has_many :citations
has_many :cited_publications, :through => :citations, :source => :reference
has_many :references, :foreign_key => "reference_id", :class_name => "Citation"
has_many :refered_publications, :through => :references, :source => :publication
end
class Citation < ActiveRecord::Base
belongs_to :publication
belongs_to :reference, :class_name => "Publication"
end
class Article < Publication
end
class Book < Publication
end
class Chapter < Publication
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "Article")
=> #<Article id: 1, ...>
>> b = Book.create!(:name => "Book")
=> #<Book id: 2, ...>
>> a.citations.create(:reference => b)
=> #<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">]
>> a.cited_publications
=> [#<Book id: 2, type: "Book", name: "Book", created_at: "2009-02-15 14:11:00", updated_at: "2009-02-15 14:11:00">]
>> a.refered_publications
=> []
>> b.cited_publications
=> []
>> b.refered_publications
=> [#<Article id: 1, type: "Article", name: "Article", created_at: "2009-02-15 14:10:51", updated_at: "2009-02-15 14:10:51">]
回答2:
Here's a solution that doesn't use Single Table Inheritance for the publications. That means that there are articles, books and chapters tables, instead of one publications table. Here are the commands to run to create the app:
$ rails myproject
$ cd myproject
$ script/generate model book name:string
$ script/generate model chapter name:string
$ script/generate model article name:string
$ script/generate model citation publication_type:string publication_id:integer reference_type:string reference_id:integer
Create this file in lib/acts_as_publication.rb:
module ActsAsPublication
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_publication
has_many :citations, :as => :publication
has_many :references, :as => :reference, :class_name => "Citation"
end
end
end
Create this file in config/initializers/acts_as_publication.rb:
ActiveRecord::Base.send(:include, ActsAsPublication)
Then call that in each model, Article, Book and Chapter, like this:
class Article < ActiveRecord::Base
acts_as_publication
end
Then add these relationships in app/models/citation.rb:
class Citation < ActiveRecord::Base
belongs_to :publication, :polymorphic => true
belongs_to :reference, :polymorphic => true
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "a")
=> #<Article id: 1, ...>
>> b = Article.create!(:name => "b")
=> #<Article id: 2, ...>
>> Citation.create!(:publication => a, :reference => b)
=> #<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, ...>]
>> Book.create!(:name => "foo")
=> #<Book id: 1, name: "foo", created_at: "2009-02-15 13:18:23", updated_at: "2009-02-15 13:18:23">
>> a.citations.create(:reference => Book.first)
=> #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">
>> Book.first.references
=> [#<Citation id: 2, ...>]
>> a.citations
=> [#<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">, #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">]
回答3:
I have an incomplete answer over at http://github.com/francois/so-536261/tree/master
Basically, the DB schema does support your use case, but ActiveRecord doesn't. The solution will probably involve using find by sql or other tricks.
来源:https://stackoverflow.com/questions/536261/tricky-active-record-relationships-polymorphic-bi-directional-self-referential