diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 914709e761c9c685e8e47f8ac9bf154c407ff3cf..e461e2ecc6dcbef053c575825b8b339a9b28a559 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 401398c56b9ed17839b621541ee50b30402b78c4..f69a14f7406369765fedcb778996cba66305b048 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 @@ def retrieve_connection_pool(klass) 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 7145dc069269b012b595de37982530bfde039375..63e402011320a5bf04d4a51788b11f3e904f4ef0 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 @@ def connection_url_to_hash(url) # :nodoc: 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 @@ def connection self.class.connection end - # Establishes the connection to the database. Accepts a hash as input where - # the :adapter 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 :adapter 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 626571a9480b1c1c7c82588cf0c5b3da001c6224..e51796871ad3bfc685774527548e97152584a89c 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 @@ 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 f092edecda5fc76603fa2374f1ab060824350119..3eec59b5a4b76779138ba402cc451ee696ba5e8a 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 Result; include Enumerable end 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 d7adcdc5d4661f7a8995461bee0543ef5fb22841..74a9be99bde4fec8c25628f85e0ab5b416d0fb19 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 @@ 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 @@ def indexes(table_name, name = nil) # 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 11bb457d033030c62adf11985c51cf8662744703..ac3fb72b6e72f2421abf8d2a11a62caa43acdea8 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 @@ 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 dfd239a9982672f176c62f430ca1fdc5206501c0..84ac6dd93d4d6e5b2904aa99f4b9540790db03eb 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -65,6 +65,11 @@ module Core # 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 @@ def arel_engine 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 de9461982aaa4dd1269d091335819490cd3f2102..9b73c0d33cdf2db836f1ba9693214b7b87058f48 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -13,7 +13,9 @@ module Inheritance 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 @@ def instantiate(record) # 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 6643c3bf5a2dd19d1f6a53360c66b3be75b91b04..9a8f7a93b6453e5593fdcb08f928914e35d612a6 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 @@ def self.included(base) 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 1de820b3a6c57d779dbbd6cc929068c1b55f4c66..5fd0b12706ef19fc8450d617f5836f6de0a45fe9 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -128,7 +128,7 @@ def quoted_table_name # 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 @@ def full_table_name_prefix #:nodoc: # 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 d4b0f236ee96826787506146c01daf054da1dd77..5f9a742285b299945e9962892e13e03bf6ddfcca 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 0000000000000000000000000000000000000000..07d538f6bc7f9ef3a9f3580b762884f1985bc98f --- /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 0000000000000000000000000000000000000000..638a1b38f843133e7fdc6b95a6f4d60cb0457acf --- /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 5933e1f46e9dd74e6563cff40f44c4ffc74bb79d..09c8c25d7487a4838c2118e31e96dbf04c318b3d 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -596,6 +596,10 @@ def create_table(*args, &block) 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