Report abuse

From 0ebe58ed686bd44273409b6c429c2d8c56f0f352 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@jeremyevans.net>
Date: Sat, 11 Jul 2009 11:09:06 -0700
Subject: [PATCH] Model associations now default to associating to classes in the same scope (Fixes #274)

This changes the default for the :class_name option to use a model in
the same scope as the current model.  This is a slight breakage of
backwards compatibility, but should only affect users who are
associating a model inside a module to a model outside the module.
Defaulting to an object in the same scope leads to less surprising
behavior.

This patch also is a cleaner implementation, since the code no longer
varies per association type.
---
 CHANGELOG                                 |    2 ++
 lib/sequel/model/associations.rb          |    4 +---
 lib/sequel/plugins/many_through_many.rb   |    1 -
 spec/extensions/many_through_many_spec.rb |   13 +++++++++++++
 spec/model/associations_spec.rb           |   20 ++++++++++++++++++++
 5 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 07e2def..7b7750a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,7 @@
 === HEAD

+* Model associations now default to associating to classes in the same scope (jeremyevans, nougad) (#274)
+
 * Add Dataset#unlimited, similar to unfiltered and unordered (jeremyevans)

 * Make Dataset#from_self take an options hash and respect an :alias option, giving the alias to use (Phrogz)
diff --git a/lib/sequel/model/associations.rb b/lib/sequel/model/associations.rb
index 24176af..275edbf 100644
--- a/lib/sequel/model/associations.rb
+++ b/lib/sequel/model/associations.rb
@@ -548,6 +548,7 @@ module Sequel
             when Class
               opts[:class_name] ||= opts[:class].name
           end
+          opts[:class_name] ||= (opts[:model].name.split("::")[0..-2] + [camelize(opts.returns_array? ? singularize(name) : name)]).join('::')

           send(:"def_#{type}", opts)

@@ -646,7 +647,6 @@ module Sequel
           left = (opts[:left_key] ||= opts.default_left_key)
           right = (opts[:right_key] ||= opts.default_right_key)
           left_pk = (opts[:left_primary_key] ||= self.primary_key)
-          opts[:class_name] ||= camelize(singularize(name))
           opts[:cartesian_product_number] ||= 1
           join_table = (opts[:join_table] ||= opts.default_join_table)
           left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -705,7 +705,6 @@ module Sequel
           opts[:key] = opts.default_key unless opts.include?(:key)
           key = opts[:key]
           opts[:cartesian_product_number] ||= 0
-          opts[:class_name] ||= camelize(name)
           opts[:dataset] ||= proc do
             klass = opts.associated_class
             klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>send(key))
@@ -750,7 +749,6 @@ module Sequel
           model = self
           key = (opts[:key] ||= opts.default_key)
           primary_key = (opts[:primary_key] ||= self.primary_key)
-          opts[:class_name] ||= camelize(singularize(name))
           opts[:dataset] ||= proc do
             klass = opts.associated_class
             klass.filter(SQL::QualifiedIdentifier.new(klass.table_name, key) => send(primary_key))
diff --git a/lib/sequel/plugins/many_through_many.rb b/lib/sequel/plugins/many_through_many.rb
index bdb18fa..490c85d 100644
--- a/lib/sequel/plugins/many_through_many.rb
+++ b/lib/sequel/plugins/many_through_many.rb
@@ -125,7 +125,6 @@ module Sequel
           name = opts[:name]
           model = self
           opts[:read_only] = true
-          opts[:class_name] ||= camelize(singularize(name))
           opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
           opts[:cartesian_product_number] ||= 2
           opts[:through] = opts[:through].map do |e|
diff --git a/spec/extensions/many_through_many_spec.rb b/spec/extensions/many_through_many_spec.rb
index 694430d..e547829 100644
--- a/spec/extensions/many_through_many_spec.rb
+++ b/spec/extensions/many_through_many_spec.rb
@@ -23,6 +23,19 @@ describe Sequel::Model, "many_through_many" do
     Object.send(:remove_const, :Tag)
   end

+  it "should default to associating to other models in the same scope" do
+    class ::AssociationModuleTest
+      class Artist < Sequel::Model
+        plugin :many_through_many
+        many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
+      end  
+      class Tag < Sequel::Model
+      end  
+    end  
+    
+    ::AssociationModuleTest::Artist.association_reflection(:tags).associated_class.should == ::AssociationModuleTest::Tag
+  end 
+
   it "should raise an error if in invalid form of through is used" do
     proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id]]}.should raise_error(Sequel::Error)
     proc{@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], {:table=>:album_tags, :left=>:album_id}]}.should raise_error(Sequel::Error)
diff --git a/spec/model/associations_spec.rb b/spec/model/associations_spec.rb
index d02725a..6a9885a 100644
--- a/spec/model/associations_spec.rb
+++ b/spec/model/associations_spec.rb
@@ -16,6 +16,26 @@ describe Sequel::Model, "associate" do
     klass.association_reflection(:"par_parent2s").associated_class.should == ParParent
   end

+  it "should default to associating to other models in the same scope" do
+    class ::AssociationModuleTest
+      class Album < Sequel::Model
+        many_to_one :artist
+        many_to_many :tags
+      end
+      class Artist< Sequel::Model
+        one_to_many :albums
+      end
+      class Tag < Sequel::Model
+        many_to_many :albums
+      end
+    end
+    
+    ::AssociationModuleTest::Album.association_reflection(:artist).associated_class.should == ::AssociationModuleTest::Artist
+    ::AssociationModuleTest::Album.association_reflection(:tags).associated_class.should == ::AssociationModuleTest::Tag
+    ::AssociationModuleTest::Artist.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
+    ::AssociationModuleTest::Tag.association_reflection(:albums).associated_class.should == ::AssociationModuleTest::Album
+  end
+
   it "should add a model_object and association_reflection accessors to the dataset, and return it with the current model object" do
     MODEL_DB.reset
     klass = Class.new(Sequel::Model(:nodes)) do
-- 
1.6.1.3