From 6468ff41971b69be9c02f33d05e94e4f8845266b Mon Sep 17 00:00:00 2001
From: Alexey Muranov <muranov@math.univ-toulouse.fr>
Date: Mon, 19 Dec 2011 16:00:24 +0100
Subject: Test fixtures with custom model and table names

Test using fixtures with random names and model names, that is not following naming conventions but using set_fixture_class instead.

It is expected that the table name be defined in the model, but this is not explicitly tested here.  This will need to be fixed.
---
 activerecord/test/cases/fixtures_test.rb           | 28 ++++++++++++++++++++++
 .../test/fixtures/admin/randomly_named_a9.yml      |  7 ++++++
 .../test/fixtures/admin/randomly_named_b0.yml      |  7 ++++++
 activerecord/test/fixtures/randomly_named_a9.yml   |  7 ++++++
 .../test/models/admin/randomly_named_c1.rb         |  3 +++
 activerecord/test/models/randomly_named_c1.rb      |  3 +++
 activerecord/test/schema/schema.rb                 |  5 ++++
 7 files changed, 60 insertions(+)
 create mode 100644 activerecord/test/fixtures/admin/randomly_named_a9.yml
 create mode 100644 activerecord/test/fixtures/admin/randomly_named_b0.yml
 create mode 100644 activerecord/test/fixtures/randomly_named_a9.yml
 create mode 100644 activerecord/test/models/admin/randomly_named_c1.rb
 create mode 100644 activerecord/test/models/randomly_named_c1.rb

diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index ba09df4b7d..d60e278d1d 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -1,6 +1,7 @@
 require 'cases/helper'
 require 'models/admin'
 require 'models/admin/account'
+require 'models/admin/randomly_named_c1'
 require 'models/admin/user'
 require 'models/binary'
 require 'models/book'
@@ -14,6 +15,7 @@ require 'models/matey'
 require 'models/parrot'
 require 'models/pirate'
 require 'models/post'
+require 'models/randomly_named_c1'
 require 'models/reply'
 require 'models/ship'
 require 'models/task'
@@ -745,3 +747,29 @@ class FixtureLoadingTest < ActiveRecord::TestCase
     ActiveRecord::TestCase.try_to_load_dependency(:works_out_fine)
   end
 end
+
+class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
+  ActiveRecord::Fixtures.reset_cache
+
+  set_fixture_class :randomly_named_a9         =>
+                        ClassNameThatDoesNotFollowCONVENTIONS,
+                    :'admin/randomly_named_a9' =>
+                        Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+                    'admin/randomly_named_b0'  =>
+                        Admin::ClassNameThatDoesNotFollowCONVENTIONS
+
+  fixtures :randomly_named_a9, 'admin/randomly_named_a9',
+           :'admin/randomly_named_b0'
+
+  def test_named_accessor_for_randomly_named_fixture_and_class
+    assert_kind_of ClassNameThatDoesNotFollowCONVENTIONS,
+                   randomly_named_a9(:first_instance)
+  end
+
+  def test_named_accessor_for_randomly_named_namespaced_fixture_and_class
+    assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+                   admin_randomly_named_a9(:first_instance)
+    assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+                   admin_randomly_named_b0(:second_instance)
+  end
+end
diff --git a/activerecord/test/fixtures/admin/randomly_named_a9.yml b/activerecord/test/fixtures/admin/randomly_named_a9.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/admin/randomly_named_a9.yml
@@ -0,0 +1,7 @@
+first_instance:
+  some_attribute: AAA
+  another_attribute: 000
+
+second_instance:
+  some_attribute: BBB
+  another_attribute: 999
diff --git a/activerecord/test/fixtures/admin/randomly_named_b0.yml b/activerecord/test/fixtures/admin/randomly_named_b0.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/admin/randomly_named_b0.yml
@@ -0,0 +1,7 @@
+first_instance:
+  some_attribute: AAA
+  another_attribute: 000
+
+second_instance:
+  some_attribute: BBB
+  another_attribute: 999
diff --git a/activerecord/test/fixtures/randomly_named_a9.yml b/activerecord/test/fixtures/randomly_named_a9.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/randomly_named_a9.yml
@@ -0,0 +1,7 @@
+first_instance:
+  some_attribute: AAA
+  another_attribute: 000
+
+second_instance:
+  some_attribute: BBB
+  another_attribute: 999
diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb
new file mode 100644
index 0000000000..2f81d5b831
--- /dev/null
+++ b/activerecord/test/models/admin/randomly_named_c1.rb
@@ -0,0 +1,3 @@
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+  self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb
new file mode 100644
index 0000000000..18a86c4989
--- /dev/null
+++ b/activerecord/test/models/randomly_named_c1.rb
@@ -0,0 +1,3 @@
+class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+  self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index b06175cd3f..b8f34bb739 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -505,6 +505,11 @@ ActiveRecord::Schema.define do
     t.string :type
   end
 
+  create_table :randomly_named_table, :force => true do |t|
+    t.string  :some_attribute
+    t.integer :another_attribute
+  end
+
   create_table :ratings, :force => true do |t|
     t.integer :comment_id
     t.integer :value
-- 
cgit v1.2.3


From e0ef0936193491689724880599ae26a8f5c2b5a6 Mon Sep 17 00:00:00 2001
From: Alexey Muranov <muranov@math.univ-toulouse.fr>
Date: Tue, 20 Dec 2011 21:06:30 +0100
Subject: Use foo/bar instead of foo_bar keys for fixtures

This solves an issue with set_fixture_class class method caused by create_fixtures method's overwriting passed to it fixture model classes, when the fixtures are "namespased": foo/bar or admin/users.

The idea is to use "foo/bar" string as the name and identifier of a
fixture file bar in directory foo.  The model class of the fixture is either set with set_fixture_class method, or otherwise inferred from its name using camelize method.

Also a bug is fixed in lines 487-489 when the table names were guessed by substitution from the fixture file names, ambiguously called  table_names, instead of being taken from fixture attributes.  Now they are taken from attributes.

I plan to submit another fix so that the fixture's table name (for
example foo_bar) be defined by the fixture's model whenever possible,
instead of inferring it from the fixture's name ("foo/bar").
---
 activerecord/lib/active_record/fixtures.rb | 37 ++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 12 deletions(-)

diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index deffc7cec5..b22692de98 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -388,10 +388,10 @@ module ActiveRecord
 
     @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
 
-    def self.find_table_name(table_name) # :nodoc:
+    def self.default_fixture_model_name(fixture_name) # :nodoc:
       ActiveRecord::Base.pluralize_table_names ?
-        table_name.to_s.singularize.camelize :
-        table_name.to_s.camelize
+        fixture_name.singularize.camelize :
+        fixture_name.camelize
     end
 
     def self.reset_cache
@@ -441,9 +441,6 @@ module ActiveRecord
 
     def self.create_fixtures(fixtures_directory, table_names, class_names = {})
       table_names = [table_names].flatten.map { |n| n.to_s }
-      table_names.each { |n|
-        class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
-      }
 
       # FIXME: Apparently JK uses this.
       connection = block_given? ? yield : ActiveRecord::Base.connection
@@ -458,11 +455,12 @@ module ActiveRecord
 
           fixture_files = files_to_read.map do |path|
             table_name = path.tr '/', '_'
+            fixture_name = path
 
-            fixtures_map[path] = ActiveRecord::Fixtures.new(
+            fixtures_map[fixture_name] = new( # ActiveRecord::Fixtures.new
               connection,
               table_name,
-              class_names[table_name.to_sym] || table_name.classify,
+              class_names[fixture_name] || default_fixture_model_name(fixture_name),
               ::File.join(fixtures_directory, path))
           end
 
@@ -723,14 +721,29 @@ module ActiveRecord
       self.use_instantiated_fixtures = false
       self.pre_loaded_fixtures = false
 
-      self.fixture_class_names = Hash.new do |h, table_name|
-        h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
+      self.fixture_class_names = Hash.new do |h, fixture_name|
+        h[fixture_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_name)
       end
     end
 
     module ClassMethods
+      # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
+      #
+      # Examples:
+      #
+      #   set_fixture_class :some_fixture        => SomeModel,
+      #                     'namespaced/fixture' => Another::Model
+      #
+      # The keys must be the fixture names, that coincide with the short paths to the fixture files.
+      #--
+      # It is also possible to pass the class name instead of the class:
+      #   set_fixture_class 'some_fixture' => 'SomeModel'
+      # I think this option is redundant, i propose to deprecate it.
+      # Isn't it easier to always pass the class itself?
+      # (2011-12-20 alexeymuranov)
+      #++
       def set_fixture_class(class_names = {})
-        self.fixture_class_names = self.fixture_class_names.merge(class_names)
+        self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
       end
 
       def fixtures(*fixture_names)
@@ -770,7 +783,7 @@ module ActiveRecord
         fixture_names = Array.wrap(fixture_names || fixture_table_names)
         methods = Module.new do
           fixture_names.each do |fixture_name|
-            fixture_name = fixture_name.to_s.tr('./', '_')
+            fixture_name = fixture_name.to_s.tr('./', '_') # TODO: use fixture_name variable for only one form of fixture names ("admin/users" for example)
 
             define_method(fixture_name) do |*fixtures|
               force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
-- 
cgit v1.2.3


From d3e6e7e484c21d40e8fb7f765099f280f5de85e0 Mon Sep 17 00:00:00 2001
From: Alexey Muranov <muranov@math.univ-toulouse.fr>
Date: Tue, 20 Dec 2011 21:32:39 +0100
Subject: Test case: fixture table name is defined in model

---
 activerecord/test/cases/fixtures_test.rb | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index d60e278d1d..859bb7992b 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -772,4 +772,9 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
     assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
                    admin_randomly_named_b0(:second_instance)
   end
+
+  def test_table_name_is_defined_in_the_model
+    assert_equal :randomly_named_table, ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+    assert_equal :randomly_named_table, Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+  end
 end
-- 
cgit v1.2.3


From 7162ea2a0c75f270eab37217d1daa0268e258e71 Mon Sep 17 00:00:00 2001
From: Alexey Muranov <muranov@math.univ-toulouse.fr>
Date: Tue, 20 Dec 2011 21:58:26 +0100
Subject: Fixture's table name be defined in the model

Made the fixture's table name be taken from its model class whenever this class responds to table_name, instead of inferring it sometimes from the fixture's pass.

The previous behavior seemed buggy because it depended on whether the model class was passed as a constant or as a name string.

Improved Fixtures#initialize.
---
 activerecord/lib/active_record/fixtures.rb | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index b22692de98..77e38e8efd 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -394,6 +394,11 @@ module ActiveRecord
         fixture_name.camelize
     end
 
+    def self.default_fixture_table_name(fixture_name) # :nodoc:
+       "#{ActiveRecord::Base.table_name_prefix}"\
+         "#{fixture_name.tr('/', '_')}#{ActiveRecord::Base.table_name_suffix}".to_sym
+    end
+
     def self.reset_cache
       @@all_cached_fixtures.clear
     end
@@ -454,12 +459,11 @@ module ActiveRecord
           fixtures_map = {}
 
           fixture_files = files_to_read.map do |path|
-            table_name = path.tr '/', '_'
             fixture_name = path
 
             fixtures_map[fixture_name] = new( # ActiveRecord::Fixtures.new
               connection,
-              table_name,
+              fixture_name,
               class_names[fixture_name] || default_fixture_model_name(fixture_name),
               ::File.join(fixtures_directory, path))
           end
@@ -504,25 +508,28 @@ module ActiveRecord
 
     attr_reader :table_name, :name, :fixtures, :model_class
 
-    def initialize(connection, table_name, class_name, fixture_path)
+    def initialize(connection, fixture_name, class_name, fixture_path)
       @connection   = connection
-      @table_name   = table_name
       @fixture_path = fixture_path
-      @name         = table_name # preserve fixture base name
+      @name         = fixture_name.tr('/', '_') # preserve fixture base name
+                                                # TODO: see how it is used and if the substitution can be avoided
       @class_name   = class_name
 
       @fixtures     = ActiveSupport::OrderedHash.new
-      @table_name   = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
 
       # Should be an AR::Base type class
       if class_name.is_a?(Class)
-        @table_name   = class_name.table_name
-        @connection   = class_name.connection
-        @model_class  = class_name
+        @model_class = class_name
       else
-        @model_class  = class_name.constantize rescue nil
+        @model_class = class_name.constantize rescue nil
       end
 
+      @connection  = model_class.connection if model_class && model_class.respond_to?(:connection)
+
+      @table_name = ( model_class.respond_to?(:table_name) ?
+                      model_class.table_name :
+                      self.class.default_fixture_table_name(fixture_name) )
+
       read_fixture_files
     end
 
-- 
cgit v1.2.3


From b52d9d0ff585f3131e26317542df2971a667d319 Mon Sep 17 00:00:00 2001
From: Alexey Muranov <alexey.muranov@gmail.com>
Date: Thu, 29 Dec 2011 16:36:00 +0100
Subject: Fixes for TestFixtures::setup_fixture_accessors

Renamed "fixture_name" to "accessor_name" and made fixture names in the form "admin/users" be used as the key for the hashes @fixture_cache and @loaded_fixtures.

Previously the variable "fixture_name" here was getting a value of the form "admin_user", and this value was then used as the hash key.
---
 activerecord/lib/active_record/fixtures.rb | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 77e38e8efd..c59c00f424 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -511,8 +511,7 @@ module ActiveRecord
     def initialize(connection, fixture_name, class_name, fixture_path)
       @connection   = connection
       @fixture_path = fixture_path
-      @name         = fixture_name.tr('/', '_') # preserve fixture base name
-                                                # TODO: see how it is used and if the substitution can be avoided
+      @name         = fixture_name
       @class_name   = class_name
 
       @fixtures     = ActiveSupport::OrderedHash.new
@@ -790,9 +789,9 @@ module ActiveRecord
         fixture_names = Array.wrap(fixture_names || fixture_table_names)
         methods = Module.new do
           fixture_names.each do |fixture_name|
-            fixture_name = fixture_name.to_s.tr('./', '_') # TODO: use fixture_name variable for only one form of fixture names ("admin/users" for example)
+            accessor_name = fixture_name.tr('/', '_').to_sym
 
-            define_method(fixture_name) do |*fixtures|
+            define_method(accessor_name) do |*fixtures|
               force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
 
               @fixture_cache[fixture_name] ||= {}
@@ -805,13 +804,13 @@ module ActiveRecord
                     @fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
                   end
                 else
-                  raise StandardError, "No fixture with name '#{fixture}' found for table '#{fixture_name}'"
+                  raise StandardError, "No entry named '#{fixture}' found for fixture collection '#{fixture_name}'"
                 end
               end
 
               instances.size == 1 ? instances.first : instances
             end
-            private fixture_name
+            private accessor_name
           end
         end
         include methods
-- 
cgit v1.2.3