From 6af7192af59602e1cbb341b8bf3452afb344eff2 Mon Sep 17 00:00:00 2001
From: Jon Leighton <j@jonathanleighton.com>
Date: Sun, 18 Dec 2011 23:35:25 +0000
Subject: I herd you like modules.

---
 activerecord/lib/active_record/base.rb             |   2 +
 .../abstract/connection_pool.rb                    |   7 +-
 .../abstract/connection_specification.rb           | 100 ++++++++++-----------
 .../connection_adapters/mysql2_adapter.rb          |   4 +-
 .../connection_adapters/mysql_adapter.rb           |   4 +-
 .../connection_adapters/postgresql_adapter.rb      |   6 +-
 .../connection_adapters/sqlite3_adapter.rb         |   4 +-
 activerecord/lib/active_record/core.rb             |  13 ++-
 activerecord/lib/active_record/inheritance.rb      |  14 ++-
 activerecord/lib/active_record/model.rb            |  11 ++-
 activerecord/lib/active_record/model_schema.rb     |   4 +-
 .../connection_specification/resolver_test.rb      |   2 +-
 activerecord/test/cases/inclusion_test.rb          |  36 ++++++++
 activerecord/test/models/teapot.rb                 |  13 +++
 activerecord/test/schema/schema.rb                 |   4 +
 15 files changed, 153 insertions(+), 71 deletions(-)
 create mode 100644 activerecord/test/cases/inclusion_test.rb
 create mode 100644 activerecord/test/models/teapot.rb

(limited to 'activerecord')

diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 914709e761..e461e2ecc6 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -328,5 +328,7 @@ module ActiveRecord #:nodoc:
   # instances in the current object space.
   class Base
     include ActiveRecord::Model
+
+    self.connection_handler = ConnectionAdapters::ConnectionHandler.new
   end
 end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 401398c56b..f69a14f740 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -371,7 +371,12 @@ connection.  For example: ActiveRecord::Base.connection.close
         pool = @class_to_pool[klass.name]
         return pool if pool
         return nil if ActiveRecord::Base == klass
-        retrieve_connection_pool klass.superclass
+
+        if klass.superclass && klass.superclass < Model
+          retrieve_connection_pool klass.superclass
+        else
+          retrieve_connection_pool ActiveRecord::Base
+        end
       end
     end
 
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 7145dc0692..63e4020113 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -1,5 +1,7 @@
+require 'active_support/core_ext/module/delegation'
+
 module ActiveRecord
-  class Base
+  module Core
     class ConnectionSpecification #:nodoc:
       attr_reader :config, :adapter_method
       def initialize (config, adapter_method)
@@ -75,12 +77,6 @@ module ActiveRecord
       end
     end
 
-    ##
-    # :singleton-method:
-    # The connection handler
-    class_attribute :connection_handler, :instance_writer => false
-    self.connection_handler = ConnectionAdapters::ConnectionHandler.new
-
     # Returns the connection currently associated with the class. This can
     # also be used to "borrow" the connection to do database work that isn't
     # easily done without going straight to SQL.
@@ -88,53 +84,53 @@ module ActiveRecord
       self.class.connection
     end
 
-    # Establishes the connection to the database. Accepts a hash as input where
-    # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
-    # example for regular databases (MySQL, Postgresql, etc):
-    #
-    #   ActiveRecord::Base.establish_connection(
-    #     :adapter  => "mysql",
-    #     :host     => "localhost",
-    #     :username => "myuser",
-    #     :password => "mypass",
-    #     :database => "somedatabase"
-    #   )
-    #
-    # Example for SQLite database:
-    #
-    #   ActiveRecord::Base.establish_connection(
-    #     :adapter => "sqlite",
-    #     :database  => "path/to/dbfile"
-    #   )
-    #
-    # Also accepts keys as strings (for parsing from YAML for example):
-    #
-    #   ActiveRecord::Base.establish_connection(
-    #     "adapter" => "sqlite",
-    #     "database"  => "path/to/dbfile"
-    #   )
-    #
-    # Or a URL:
-    #
-    #   ActiveRecord::Base.establish_connection(
-    #     "postgres://myuser:mypass@localhost/somedatabase"
-    #   )
-    #
-    # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
-    # may be returned on an error.
-    def self.establish_connection(spec = ENV["DATABASE_URL"])
-      resolver = ConnectionSpecification::Resolver.new spec, configurations
-      spec = resolver.spec
-
-      unless respond_to?(spec.adapter_method)
-        raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
-      end
+    module ClassMethods
+      # Establishes the connection to the database. Accepts a hash as input where
+      # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
+      # example for regular databases (MySQL, Postgresql, etc):
+      #
+      #   ActiveRecord::Base.establish_connection(
+      #     :adapter  => "mysql",
+      #     :host     => "localhost",
+      #     :username => "myuser",
+      #     :password => "mypass",
+      #     :database => "somedatabase"
+      #   )
+      #
+      # Example for SQLite database:
+      #
+      #   ActiveRecord::Base.establish_connection(
+      #     :adapter => "sqlite",
+      #     :database  => "path/to/dbfile"
+      #   )
+      #
+      # Also accepts keys as strings (for parsing from YAML for example):
+      #
+      #   ActiveRecord::Base.establish_connection(
+      #     "adapter" => "sqlite",
+      #     "database"  => "path/to/dbfile"
+      #   )
+      #
+      # Or a URL:
+      #
+      #   ActiveRecord::Base.establish_connection(
+      #     "postgres://myuser:mypass@localhost/somedatabase"
+      #   )
+      #
+      # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
+      # may be returned on an error.
+      def establish_connection(spec = ENV["DATABASE_URL"])
+        resolver = ConnectionSpecification::Resolver.new spec, configurations
+        spec = resolver.spec
+
+        unless respond_to?(spec.adapter_method)
+          raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
+        end
 
-      remove_connection
-      connection_handler.establish_connection name, spec
-    end
+        remove_connection
+        connection_handler.establish_connection name, spec
+      end
 
-    class << self
       # Returns the connection currently associated with the class. This can
       # also be used to "borrow" the connection to do database work unrelated
       # to any of the specific Active Records.
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 626571a948..e51796871a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -4,9 +4,9 @@ gem 'mysql2', '~> 0.3.10'
 require 'mysql2'
 
 module ActiveRecord
-  class Base
+  module Core::ClassMethods
     # Establishes a connection to the database that's used by all Active Record objects.
-    def self.mysql2_connection(config)
+    def mysql2_connection(config)
       config[:username] = 'root' if config[:username].nil?
 
       if Mysql2::Client.const_defined? :FOUND_ROWS
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index f092edecda..3eec59b5a4 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -18,9 +18,9 @@ class Mysql
 end
 
 module ActiveRecord
-  class Base
+  module Core::ClassMethods
     # Establishes a connection to the database that's used by all Active Record objects.
-    def self.mysql_connection(config) # :nodoc:
+    def mysql_connection(config) # :nodoc:
       config = config.symbolize_keys
       host     = config[:host]
       port     = config[:port]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index d7adcdc5d4..74a9be99bd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -7,9 +7,9 @@ gem 'pg', '~> 0.11'
 require 'pg'
 
 module ActiveRecord
-  class Base
+  module Core::ClassMethods
     # Establishes a connection to the database that's used by all Active Record objects
-    def self.postgresql_connection(config) # :nodoc:
+    def postgresql_connection(config) # :nodoc:
       config = config.symbolize_keys
       host     = config[:host]
       port     = config[:port] || 5432
@@ -876,7 +876,7 @@ module ActiveRecord
           # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
           desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
           orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
-      
+
           column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
         end.compact
       end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 11bb457d03..ac3fb72b6e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -4,9 +4,9 @@ gem 'sqlite3', '~> 1.3.5'
 require 'sqlite3'
 
 module ActiveRecord
-  class Base
+  module Core::ClassMethods
     # sqlite3 adapter reuses sqlite_connection.
-    def self.sqlite3_connection(config) # :nodoc:
+    def sqlite3_connection(config) # :nodoc:
       # Require database.
       unless config[:database]
         raise ArgumentError, "No database file specified. Missing argument: database"
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index dfd239a998..84ac6dd93d 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -65,6 +65,11 @@ module ActiveRecord
       # Specify whether or not to use timestamps for migration versions
       cattr_accessor :timestamped_migrations , :instance_writer => false
       self.timestamped_migrations = true
+
+      ##
+      # :singleton-method:
+      # The connection handler
+      class_attribute :connection_handler, :instance_writer => false
     end
 
     module ClassMethods
@@ -111,7 +116,13 @@ module ActiveRecord
           if self == ActiveRecord::Base
             ActiveRecord::Base
           else
-            connection_handler.connection_pools[name] ? self : superclass.arel_engine
+            if connection_handler.connection_pools[name]
+              self
+            elsif superclass < ActiveRecord::Model
+              superclass.arel_engine
+            else
+              ActiveRecord::Base
+            end
           end
         end
       end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index de9461982a..9b73c0d33c 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -13,7 +13,9 @@ module ActiveRecord
     module ClassMethods
       # True if this isn't a concrete subclass needing a STI type condition.
       def descends_from_active_record?
-        if superclass.abstract_class?
+        if !(superclass < Model)
+          true
+        elsif superclass.abstract_class?
           superclass.descends_from_active_record?
         else
           superclass == Base || !columns_hash.include?(inheritance_column)
@@ -84,10 +86,14 @@ module ActiveRecord
       # Returns the class descending directly from ActiveRecord::Base or an
       # abstract class, if any, in the inheritance hierarchy.
       def class_of_active_record_descendant(klass)
-        if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
-          klass
-        elsif klass.superclass.nil?
+        unless klass < Model::Tag
           raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
+        end
+
+        if klass == Base || klass.superclass == Base ||
+           klass.superclass < Model::Tag && klass.superclass.abstract_class? ||
+           !(klass.superclass < Model::Tag)
+          klass
         else
           class_of_active_record_descendant(klass.superclass)
         end
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 6643c3bf5a..9a8f7a93b6 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -1,7 +1,14 @@
 module ActiveRecord
   module Model
+    # So we can recognise an AR class even while self.included is being
+    # executed. (At that time, klass < Model == false.)
+    module Tag #:nodoc:
+    end
+
     def self.included(base)
       base.class_eval do
+        include Tag
+
         include ActiveRecord::Persistence
         extend ActiveModel::Naming
         extend QueryCache::ClassMethods
@@ -35,10 +42,12 @@ module ActiveRecord
         include Aggregations, Transactions, Reflection, Serialization, Store
 
         include Core
+
+        self.connection_handler = ActiveRecord::Base.connection_handler
       end
     end
   end
 end
 
 require 'active_record/connection_adapters/abstract/connection_specification'
-ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Model)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 1de820b3a6..5fd0b12706 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -128,7 +128,7 @@ module ActiveRecord
 
       # Computes the table name, (re)sets it internally, and returns it.
       def reset_table_name #:nodoc:
-        if superclass.abstract_class?
+        if (superclass < ActiveRecord::Model) && superclass.abstract_class?
           self.table_name = superclass.table_name || compute_table_name
         elsif abstract_class?
           self.table_name = superclass == Base ? nil : superclass.table_name
@@ -143,7 +143,7 @@ module ActiveRecord
 
       # The name of the column containing the object's class when Single Table Inheritance is used
       def inheritance_column
-        if self == Base
+        if self == Base || !(superclass < Model)
           'type'
         else
           (@inheritance_column ||= nil) || superclass.inheritance_column
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index d4b0f236ee..5f9a742285 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -1,7 +1,7 @@
 require "cases/helper"
 
 module ActiveRecord
-  class Base
+  module Core
     class ConnectionSpecification
       class ResolverTest < ActiveRecord::TestCase
         def resolve(spec)
diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb
new file mode 100644
index 0000000000..07d538f6bc
--- /dev/null
+++ b/activerecord/test/cases/inclusion_test.rb
@@ -0,0 +1,36 @@
+require 'cases/helper'
+require 'models/teapot'
+
+class BasicInclusionModelTest < ActiveRecord::TestCase
+  def test_basic_model
+    Teapot.create!(:name => "Ronnie Kemper")
+    assert_equal "Ronnie Kemper", Teapot.find(1).name
+  end
+end
+
+class InclusionUnitTest < ActiveRecord::TestCase
+  def setup
+    @klass = Class.new { include ActiveRecord::Model }
+  end
+
+  def test_non_abstract_class
+    assert !@klass.abstract_class?
+  end
+
+  def test_abstract_class
+    @klass.abstract_class = true
+    assert @klass.abstract_class?
+  end
+
+  def test_establish_connection
+    assert @klass.respond_to?(:establish_connection)
+  end
+
+  def test_adapter_connection
+    assert @klass.respond_to?("#{ActiveRecord::Base.connection_config[:adapter]}_connection")
+  end
+
+  def test_connection_handler
+    assert_equal ActiveRecord::Base.connection_handler, @klass.connection_handler
+  end
+end
diff --git a/activerecord/test/models/teapot.rb b/activerecord/test/models/teapot.rb
new file mode 100644
index 0000000000..638a1b38f8
--- /dev/null
+++ b/activerecord/test/models/teapot.rb
@@ -0,0 +1,13 @@
+class Teapot
+  # I'm a little teapot,
+  # Short and stout,
+  # Here is my handle
+  # Here is my spout
+  # When I get all steamed up,
+  # Hear me shout,
+  # Tip me over and pour me out!
+  #
+  # HELL YEAH TEAPOT SONG
+
+  include ActiveRecord::Model
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 5933e1f46e..09c8c25d74 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -596,6 +596,10 @@ ActiveRecord::Schema.define do
     t.datetime :ending
   end
 
+  create_table :teapots, :force => true do |t|
+    t.string :name
+  end
+
   create_table :topics, :force => true do |t|
     t.string   :title
     t.string   :author_name
-- 
cgit v1.2.3