Inheritance via Though Associations?


#1

I posted something about this a week ago wihtout response, but have made
some
progress since. However, I’m still not getting exactly what I want.

OK, three core tables:

create_table :reference_items do |t|
  t.column :title,             :string, :limit => 255
  t.column :year,              :integer, :limit => 4
  t.column :type,              :string
  t.column :container_id,      :integer
  t.column :collection_id,     :integer
  t.column :original_id,       :integer
  t.column :event_id,          :integer
end

create_table :contributors do |t|
  t.column :type,              :string, :default => "Person"
  t.column :sort_name,         :string, :limit => 255, :null => 

false
end

create_table :contributions do |t|
  t.column :reference_item_id, :integer
  t.column :agent_id,          :integer
  t.column :type,              :string
  t.column :position,          :integer
end

Drawing on this article …

http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness

… I have no problem using the through relation to be able to do stuff
like:

some_refitem.contributors.first.sort_name

Where I get stuck is that I need to be able to subclass contributions so
that I
can do stuff like:

some_refitem.authors
some_refitem.translators

So I need to be able to essentially treat each subclass as a list.

Can STI + through associations currently handle this, and is yes, how?

If not, any suggestions on how to achieve what I’m after?

Bruce


#2

Bruce D’Arcus wrote:

Where I get stuck is that I need to be able to subclass contributions so
that I can do stuff like:

some_refitem.authors
some_refitem.translators

So I need to be able to essentially treat each subclass as a list.

Can STI + through associations currently handle this, and is yes, how?

If not, any suggestions on how to achieve what I’m after?

Glad my blog was useful for you. I think the entry on working with
polymorphic :through associations should be useful as well.
http://blog.hasmanythrough.com/articles/2006/04/03/polymorphic-through
Now that I think of it, you can adapt that technique to work with
concrete classes in STI too. Just define an association with a condition
specifying the concrete class in the type field. Something like:

has_many :contributors, :through => :contributions
has_many :authors, : through => :contributions, :condition => “type =
‘Author’”
has_many : translators, : through => :contributions, :condition =>
“type = ‘Translator’”

I notice you have a ‘type’ in contributions and contributors both. You
really need both of those? I’m not sure which type you want to make the
different associations based on. You may need to change my example
around depending on which. (Including your model class code in the
question would be helpful.)

Also, it looks like contributions has a position for acting as a list.
If you really want each concrete class to act as a different list, you
might try defining the acts_as_list using a :scope that includes the
‘type’ of the record. See the docs on acts_as_list for creating scopes
as conditions.


Josh S.
http://blog.hasmanythrough.com


#3

Josh S. <josh@…> writes:

Now that I think of it, you can adapt that technique to work with
concrete classes in STI too. Just define an association with a condition
specifying the concrete class in the type field. Something like:

has_many :contributors, :through => :contributions
has_many :authors, : through => :contributions, :condition => “type =
‘Author’”
has_many : translators, : through => :contributions, :condition =>
“type = ‘Translator’”

Not quite working.

Schema:

create_table “contributions”, :force => true do |t|
t.column “reference_item_id”, :integer
t.column “contributor_id”, :integer
t.column “type”, :string
t.column “position”, :integer
end

create_table “contributors”, :force => true do |t|
t.column “name”, :string
t.column “type”, :string, :default => “Person”
end

create_table “reference_items”, :force => true do |t|
t.column “title”, :string
t.column “type”, :string
end

Models:

class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => true
has_many :reference_items, :through => :contributions
end

class Contribution < ActiveRecord::Base
belongs_to :reference_item
belongs_to :contributor
end

class Author < Contribution
acts_as_list :scope => “type = ‘Author’”
end

class ReferenceItem < ActiveRecord::Base
has_many :contributors,
:through => :contributions
has_many :authors,
:through => :contributions,
:condition => “type = ‘Author’”
end

Book and Person are just straight subclasses of ReferenceItem and
Contributor
respectively.

So now on the console:

book = Book.new(:title => “Book Title”)
=> #<Book:0x247d2d0 @attributes={“title”=>“Book Title”, “type”=>“Book”},
@new_record=true>

book.save
=> true

jd = Person.new(:name => “Jane Doe”)
=> #<Person:0x25f85c4 @attributes={“name”=>“Jane Doe”,
“type”=>“Person”},
@new_record=true>

jd.save
=> true

author = Author.new(:reference_item_id => 1, :contributor_id => 1)
=> #<Author:0x25e40ec @attributes={“reference_item_id”=>1,
“type”=>“Author”,
“position”=>nil,
“contributor_id”=>1}, @new_record=true>

author.save
=> true

x = Book.find(1)
=> #<Book:0x25be888 @attributes={“title”=>“Book Title”, “type”=>“Book”,
“id”=>“1”}>

x.authors
NoMethodError: undefined method `authors’ for #Book:0x25be888

x.contributors
ActiveRecord::HasManyThroughAssociationNotFoundError:
ActiveRecord::HasManyThroughAssociationNotFoundError

I get the same error if I am using the main ReferenceItem class. Am I
missing
something obvious?

Bruce


#4

Bruce D’Arcus <bdarcus.lists@…> writes:

Not quite working.

For benefit of the archives, the solution is …

class ReferenceItem < ActiveRecord::Base
has_many :contributors,
:through => :contributions
has_many :authors,
:through => :contributions,
:condition => “type = ‘Author’”
end

To replace the “condition” with “:source => :contributor”.

This now works correctly:

x.authors.first.name

Bruce


#5

Josh S. <josh@…> writes:

[… snip …]

Now that I think of it, you can adapt that technique to work with
concrete classes in STI too. Just define an association with a condition
specifying the concrete class in the type field. Something like:

has_many :contributors, :through => :contributions
has_many :authors, : through => :contributions, :condition => “type =
‘Author’”
has_many : translators, : through => :contributions, :condition =>
“type = ‘Translator’”

Thanks. I was missing the :condition; will give it a go.

I notice you have a ‘type’ in contributions and contributors both. You
really need both of those? I’m not sure which type you want to make the
different associations based on. You may need to change my example
around depending on which. (Including your model class code in the
question would be helpful.)

The way I have it now (not including any associations) is:

Contributor < ActiveRecord::Base
end

Person < Contributor
end

Organization < Contributor
end

[aside: it would be nice if AR would allow me to store types in separate
tables
by default]

But a given contributor can play different roles in different
circumstances, so
they are not subclasses of Contributor. For example, I can be a
translator and
an author, and an editor.

So I have then have that captured in the Contribution class:

Contribution < ActiveRecord::Base
end

Authorship < Contribution
end

Editorship < Contribution
end

Translation < Contribution
end

So the idea is that translators, authors, editors, publishers, etc. (all
of
which I need to handle) are each different roles that associate a
contributor to
an item.

I’m not sure that’s the best way to do that (am new to this), but it
seems like
it is to me (?).

Also, it looks like contributions has a position for acting as a list.

Correct. I need to treat each subclass as a separate list.

If you really want each concrete class to act as a different list, you
might try defining the acts_as_list using a :scope that includes the
‘type’ of the record.

OK, thanks!

Bruce