#426 ✓not-applicable
Brenden Grace

Many to many through a join model

Reported by Brenden Grace | June 30th, 2008 @ 06:37 PM

It appears that currently the only way to get Many To Many associations to work is by letting DM build the join table for you:

class Photo
  include DataMapper::Resource
  property :id, Serial
  has n, :taggings
  has n, :tags, :through => Resource
end

class Tag
  include DataMapper::Resource
  property :id, Serial
  has n, :taggings
  has n, :photos, :through => Resource
end

The patch included allows this to work:

class Tagging
  include DataMapper::Resource
  property :photo_id, Integer, :key => true
  property :tag_id, Integer, :key => true
  belongs_to :photo
  belongs_to :tag
end

class Photo
  include DataMapper::Resource
  property :id, Serial
  has n, :taggings
  has n, :tags, :through => Resource, :class_name => 'Tagging'
end

class Tag
  include DataMapper::Resource
  property :id, Serial
  has n, :taggings
  has n, :photos, :through => Resource, :class_name => 'Tagging'
end

This is my first patch to DM so I wanted to float it past everyone before I went to the trouble of writing specs. Is this the way to go? Is someone already working on this?

http://brendengrace.com/blog/200...

--- dm-core.orig/lib/dm-core/associations/many_to_many.rb	2008-06-30 13:07:10.000000000 -0400
+++ dm-core/lib/dm-core/associations/many_to_many.rb	2008-06-30 18:36:08.000000000 -0400
@@ -38,6 +38,7 @@
         EOS
 
         opts = options.dup
+        model_name = opts.delete(:class_name)
         opts.delete(:through)
         opts[:child_model]              ||= opts.delete(:class_name)  || Extlib::Inflection.classify(name)
         opts[:parent_model]             =   model.name
@@ -48,16 +49,21 @@
         opts[:mutable]                  =   true
 
         names        = [ opts[:child_model], opts[:parent_model] ].sort
-        model_name   = names.join
-        storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
+        if model_name
+          storage_name = Extlib::Inflection.tableize(model_name)
+        else
+          model_name   = names.join
 
+          storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
+        end
         opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
 
         model.has(model.n, opts[:near_relationship_name])
 
         relationship = model.relationships(repository_name)[name] = RelationshipChain.new(opts)
 
-        unless Object.const_defined?(model_name)
+        if not Object.const_defined?(model_name) or \
+          not Object.const_get(model_name).respond_to?("many_to_many")
           model = DataMapper::Model.new(storage_name)
 
           model.class_eval <<-EOS, __FILE__, __LINE__

Comments and changes to this ticket

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) July 1st, 2008 @ 12:12 AM

    I'm wondering why we can't make it so using :through => :taggings will create a regular many to many association, that would seem like the most natural way for it to work from the POV of a casual DM user.

    I always thought the plan was to make it so a :through relationship -- where the join model that only contains properties that link to either side of the relationship -- will work the same as our many to many relationship works now.

  • Brenden Grace

    Brenden Grace July 1st, 2008 @ 07:40 AM

    My concern would be that one may want to model the join table for some reason. Maybe there are attributes assigned to the association that need to be included for example. It seems based on the docs at http://datamapper.org/docs/assoc... , that the :through => :somethings is used only to point to a table (below).

    
    class Tag
      include DataMapper::Resource 
      has n, :taggings
      has n, :photos, :through => :taggings
    end
    class Tagging
      include DataMapper::Resource
     
      belongs_to :tag
      belongs_to :photo
    end
    
    

    I had another modification that used :through => ClassName, but that I assumed would be rejected based on the direction the API seems to be going. I simply would like (and need!) DM to support the modeling of join tables so that they can be customized. I like and use the "automagic" joins, but I think we need both options. So how best do we do this?

  • Sam Smoot

    Sam Smoot July 1st, 2008 @ 09:52 AM

    • State changed from “new” to “open”

    :through and many_to_many are supposed to be the same thing.

    So before integrating this work, we need to figure out where they're diverging. In your case *Brenden*, :through => :taggings should be what you're asking for. It should inflect the Tagging class from :taggings and use that.

    :through => ClassName is acceptable. It's not a DM concern as much as it's a Ruby load-order concern (and we don't use const_missing like AR to load missing constants for you).

    So, if you could describe what :through => :taggings (along with defining a Tagging class) doesn't work for you, I'd appreciate it.

  • Brenden Grace

    Brenden Grace July 1st, 2008 @ 10:20 AM

    In associations.rb:94

    klass = ManyToMany if options[:through] == DataMapper::Resource
    

    This led me to believe that :through => DataMapper::Resource was the only 'blessed' way to do many-to-many.

    class Tagging
      include DataMapper::Resource
      property :photo_id, Integer, :key => true
      property :tag_id, Integer, :key => true
      belongs_to :photo
      belongs_to :tag
    end
    
    class Photo
      include DataMapper::Resource
      property :id, Serial
      has n, :taggings
      #has n, :tags, :through => Resource, :class_name => 'Tagging'
      has n, :tags, :through => :taggings
    end
    
    class Tag
      include DataMapper::Resource
      property :id, Serial
      has n, :taggings
      #has n, :photos, :through => Resource, :class_name => 'Tagging'
      has n, :photos, :through => :taggings
    end
    

    Works until you try to do something like:

    tag.photos << Photo.new
    
    # Output
    
    dm-core-0.9.3/lib/dm-core/associations/one_to_many.rb:229:in `assert_mutable': You can not modify this assocation (DataMapper::Associations::ImmutableAssociationError)
      from dm-core-0.9.3/lib/dm-core/associations/one_to_many.rb:89:in `<<'
      from dmtest2.rb:44
    

    I assume that from your comments that this is not the correct behavior and :through => :taggings should be falling into many_to_many.rb.

    My only beef with this syntax is if we are going to the trouble of creating a model class, shouldn't we be pointing to that instead of the underlying table that is created by the model class?

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 7th, 2009 @ 04:10 PM

    • Tag changed from feature, has_and_belongs_to_many, patch, suggestion to feature, has_and_belongs_to_many, patch
    • State changed from “open” to “not-applicable”
    • Assigned user cleared.

    This ticket looks to be a bit old, so I am going to close it with some comments.

    Anytime you use :through with the has() command you're asking for a many to many association. When you say :through => Resource, DM will generate a dynamic model for the join. When you say :through => :assoc, it will use the existing association through an explicit join model, which I think is what you want.

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

People watching this ticket

Pages