Forum: Ruby on Rails Inheritance via Though Associations?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
3f2ae5bbf68f4b4d546b23b6b4087b54?d=identicon&s=25 Bruce D'Arcus (Guest)
on 2006-04-09 17:28
(Received via mailing list)
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...

... 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
9f0f89bbd9e1ecfbaab6584e429b7a2f?d=identicon&s=25 Josh Susser (jsusser)
on 2006-04-09 19:11
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...
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 Susser
http://blog.hasmanythrough.com
3f2ae5bbf68f4b4d546b23b6b4087b54?d=identicon&s=25 Bruce =?utf-8?b?RFwnQXJjdXM=?= (Guest)
on 2006-04-09 19:34
(Received via mailing list)
Josh Susser <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
3f2ae5bbf68f4b4d546b23b6b4087b54?d=identicon&s=25 Bruce D'Arcus (Guest)
on 2006-04-10 18:40
(Received via mailing list)
Josh Susser <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
3f2ae5bbf68f4b4d546b23b6b4087b54?d=identicon&s=25 Bruce =?utf-8?b?RFwnQXJjdXM=?= (Guest)
on 2006-04-11 00:45
(Received via mailing list)
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
This topic is locked and can not be replied to.