Forum: Ruby on Rails Why are double sided polymorphic relationships lacking in Rails?

Posted by tsenart (Guest)
on 2010-02-08 23:11
(Received via mailing list)
http://stackoverflow.com/questions/2224994/why-are-double-sided-polymorphic-relationships-lacking-in-rails
Posted by Mat Brown (Guest)
on 2010-02-08 23:13
(Received via mailing list)
Inverse associations will be standard in Rails 2.3.6. For the
impatient, here's a backport:

http://github.com/oggy/inverse_of

Mat
Posted by tsenart (Guest)
on 2010-02-09 09:57
(Received via mailing list)
Very cool! Thanks for the answer. But inverse associations are only a
component of those double sided polymorphic associations.
At the present you can only make one sided associations. Table talking
it means the following:

Single sided polymorphic:
 -----------------------------------------------
| origin_id | destination_id | destination_type |
 -----------------------------------------------

Double sided polymorphic:
 -------------------------------------------------------------
| origin_id | origin_type | destination_id | destination_type |
 -------------------------------------------------------------

This way there's only on table to track all these relations. It allows
every model to be associated with every other model as many times as
needed.
For a better explanation I wrote something on Stackoverflow -> Here is
the link again:
http://stackoverflow.com/questions/2224994/why-are-double-sided-polymorphic-relationships-lacking-in-rails&usg=AFQjCNEK8nv15YTOV3IYYh-2od_6Ng4Eug
Posted by tsenart (Guest)
on 2010-02-09 11:42
(Received via mailing list)
ActiveRecord supports one-to-many polymorphic associations but not
many-to-many polymorphic associations. Thats what I wanted.
I emulate that behavior like this:
We have three models: - Article, Asset and Relationship

class Article < PolyRecord
  habtm_polymorphs [:assets]
end

class Asset < PolyRecord
  habtm_polymorphs [:articles]
end

class Relationship < ActiveRecord::Base
  belongs_to :origin, :polymorphic => true
  belongs_to :destination, :polymorphic => true

  after_create :create_reverse_relationship

private
  def create_reverse_relationship
    rev = Relationship.new :origin => self.destination, :destination
=> self.origin
    rev.save(false)
    true
  end

end

class PolyRecord < ActiveRecord::Base
  self.abstract_class = true

  def self.habtm_polymorphs(associations, options={})
    associations = [associations].flatten
    options[:polymorphic_join_table] ||= 'relationships'
    options[:polymorphic_from] ||= 'origin'
    options[:polymorphic_to] ||= 'destination'

    pjoin = options[:polymorphic_join_table]
    pto   = options[:polymorphic_to]
    pfrom = options[:polymorphic_from]

    has_many pjoin, :as => pto
    has_many pjoin, :as => pfrom

    associations.each do |assoc|
      has_many assoc, :through => pjoin, :source => pto, :source_type
=> assoc.to_s.singularize.camelize
    end

    after_destroy do |obj|
      Relationship.delete_all({
        :destination_id => obj.id,
        :destination_type => obj.class.to_s
      })

      Relationship.delete_all({
        :origin_id => obj.id,
        :origin_type => obj.class.to_s
      })
    end

    associations.each do |assoc|
      define_method "#{assoc}=".to_sym do |args|
        eval "self.#{assoc}.clear"
        args = [args].flatten

        Relationship.delete_all({
          :origin_type => assoc.to_s.singularize.capitalize,
          :destination_id => self.id,
          :destination_type => self.class.to_s })

        Relationship.destroy_all({
          :origin_id => self.id,
          :origin_type => self.class.to_s,
          :destination_type => assoc.to_s.singularize.camelize })

        eval "self.#{assoc} << args" unless args.empty?
      end
    end

  end
end


Then you can use it like this:
@asset = Asset.new
@asset.articles = Article.first
@asset.articles = Article.all
@asset.articles << Article.new
@article.assets =  @asset.articles.first.assets.last
Posted by Mat Brown (Guest)
on 2010-02-09 12:47
(Received via mailing list)
On Tue, Feb 9, 2010 at 05:40, tsenart <tsenart@gmail.com> wrote:
>  habtm_polymorphs [:articles]
>    rev = Relationship.new :origin => self.destination, :destination
>  def self.habtm_polymorphs(associations, options={})
>    has_many pjoin, :as => pfrom
>      })
>        args = [args].flatten
>
> @asset.articles = Article.first
>>
>> This way there's only on table to track all these relations. It allows
>> > impatient, here's a backport:
>> > > To post to this group, send email to rubyonrails-talk@googlegroups.com.
>> > > To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com.
>> > > For more options, visit this group athttp://groups.google.com/group/rubyonrails-talk?hl=en.
>
> --
> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
> To post to this group, send email to rubyonrails-talk@googlegroups.com.
> To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
>
>

Ah, I see what you're getting at.

When I've had to do that, I use full-blown model for the join table,
and then a has_many :through to define the relationship between the
endpoints. It's not a perfect solution but probably the best that's
possible in AR.
Posted by Marnen Laibow-Koser (marnen)
on 2010-02-09 14:37
Mat Brown wrote:
> On Tue, Feb 9, 2010 at 05:40, tsenart <tsenart@gmail.com> wrote:
>> �habtm_polymorphs [:articles]
>> � �rev = Relationship.new :origin => self.destination, :destination
>> �def self.habtm_polymorphs(associations, options={})
>> � �has_many pjoin, :as => pfrom
>> � � �})
>> � � � �args = [args].flatten
>>
>> @asset.articles = Article.first
>>>
>>> This way there's only on table to track all these relations. It allows
>>> > impatient, here's a backport:
>>> > > To post to this group, send email to rubyonrails-talk@googlegroups.com.
>>> > > To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com.
>>> > > For more options, visit this group athttp://groups.google.com/group/rubyonrails-talk?hl=en.
>>
>> --
>> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
>> To post to this group, send email to rubyonrails-talk@googlegroups.com.
>> To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com.
>> For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
>>
>>
> 
> Ah, I see what you're getting at.
> 
> When I've had to do that, I use full-blown model for the join table,
> and then a has_many :through to define the relationship between the
> endpoints. It's not a perfect solution but probably the best that's
> possible in AR.

Would has_many_polymorphs help?

Best,
-- 
Marnen Laibow-Koser
http://www.marnen.org
marnen@marnen.org
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.