From 6af7192af59602e1cbb341b8bf3452afb344eff2 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 18 Dec 2011 23:35:25 +0000 Subject: [PATCH] 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 +- .../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 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 @@ 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 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 @@ 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 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 @@ 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 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 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 @@ 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 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 @@ 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 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 de9461982a..9b73c0d33c 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 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 @@ 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 1de820b3a6..5fd0b12706 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 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 @@ 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 -- GitLab