From 93c1f11c0a5097a5431819a1551a02a869a16a38 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 28 Dec 2011 15:38:16 +0000 Subject: [PATCH] Support configuration on ActiveRecord::Model. The problem: We need to be able to specify configuration in a way that can be inherited to models that include ActiveRecord::Model. So it is no longer sufficient to put 'top level' config on ActiveRecord::Base, but we do want configuration specified on ActiveRecord::Base and descendants to continue to work. So we need something like class_attribute that can be defined on a module but that is inherited when ActiveRecord::Model is included. The solution: added ActiveModel::Configuration module which provides a config_attribute macro. It's a bit specific hence I am not putting this in Active Support or making it a 'public API' at present. --- activemodel/lib/active_model.rb | 1 + .../lib/active_model/attribute_methods.rb | 3 +- activemodel/lib/active_model/configuration.rb | 134 +++++++++++++++ .../active_model/mass_assignment_security.rb | 22 +-- .../lib/active_model/serializers/json.rb | 3 +- activemodel/lib/active_model/validations.rb | 5 +- activemodel/test/cases/configuration_test.rb | 154 ++++++++++++++++++ activerecord/lib/active_record.rb | 1 - .../active_record/attribute_methods/dirty.rb | 2 +- .../active_record/attribute_methods/read.rb | 5 +- .../attribute_methods/serialization.rb | 2 +- .../attribute_methods/time_zone_conversion.rb | 7 +- activerecord/lib/active_record/base.rb | 1 - activerecord/lib/active_record/callbacks.rb | 51 ++++-- .../lib/active_record/configuration.rb | 36 ---- activerecord/lib/active_record/core.rb | 125 +++++++------- activerecord/lib/active_record/explain.rb | 8 +- activerecord/lib/active_record/inheritance.rb | 6 +- .../lib/active_record/locking/optimistic.rb | 5 +- activerecord/lib/active_record/model.rb | 91 ++++++----- .../lib/active_record/model_schema.rb | 24 +-- .../lib/active_record/nested_attributes.rb | 2 +- .../lib/active_record/readonly_attributes.rb | 2 +- activerecord/lib/active_record/reflection.rb | 3 +- .../lib/active_record/scoping/default.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 2 +- .../test/cases/attribute_methods/read_test.rb | 1 - activerecord/test/cases/configuration_test.rb | 26 --- 28 files changed, 500 insertions(+), 224 deletions(-) create mode 100644 activemodel/lib/active_model/configuration.rb create mode 100644 activemodel/test/cases/configuration_test.rb delete mode 100644 activerecord/lib/active_record/configuration.rb delete mode 100644 activerecord/test/cases/configuration_test.rb diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index d0e2a6f39c..9fc308375f 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -32,6 +32,7 @@ module ActiveModel autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks + autoload :Configuration autoload :Conversion autoload :Dirty autoload :EachValidator, 'active_model/validator' diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index d7ec7e6ca5..71ab1501c8 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -61,7 +61,8 @@ module ActiveModel CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ included do - class_attribute :attribute_method_matchers, :instance_writer => false + extend ActiveModel::Configuration + config_attribute :attribute_method_matchers self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new] end diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb new file mode 100644 index 0000000000..1757c12ebf --- /dev/null +++ b/activemodel/lib/active_model/configuration.rb @@ -0,0 +1,134 @@ +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/class/attribute_accessors' + +module ActiveModel + # This API is for Rails' internal use and is not currently considered 'public', so + # it may change in the future without warning. + # + # It creates configuration attributes that can be inherited from a module down + # to a class that includes the module. E.g. + # + # module MyModel + # extend ActiveModel::Configuration + # config_attribute :awesome + # self.awesome = true + # end + # + # class Post + # include MyModel + # end + # + # Post.awesome # => true + # + # Post.awesome = false + # Post.awesome # => false + # MyModel.awesome # => true + # + # We assume that the module will have a ClassMethods submodule containing methods + # to be transferred to the including class' singleton class. + # + # Config options can also be defined directly on a class: + # + # class Post + # extend ActiveModel::Configuration + # config_attribute :awesome + # end + # + # So this allows us to define a module that doesn't care about whether it is being + # included in a class or a module: + # + # module Awesomeness + # extend ActiveSupport::Concern + # + # included do + # extend ActiveModel::Configuration + # config_attribute :awesome + # self.awesome = true + # end + # end + # + # class Post + # include Awesomeness + # end + # + # module AwesomeModel + # include Awesomeness + # end + module Configuration #:nodoc: + def config_attribute(name, options = {}) + klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute + klass.new(self, name, options).define + end + + class Attribute + attr_reader :host, :name, :options + + def initialize(host, name, options) + @host, @name, @options = host, name, options + end + + def instance_writer? + options.fetch(:instance_writer, false) + end + end + + class ClassAttribute < Attribute + def define + if options[:global] + host.cattr_accessor name, :instance_writer => instance_writer? + else + host.class_attribute name, :instance_writer => instance_writer? + end + end + end + + class ModuleAttribute < Attribute + def class_methods + @class_methods ||= begin + if host.const_defined?(:ClassMethods, false) + host.const_get(:ClassMethods) + else + host.const_set(:ClassMethods, Module.new) + end + end + end + + def define + host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + attr_accessor :#{name} + def #{name}?; !!#{name}; end + CODE + + name, host = self.name, self.host + + class_methods.class_eval do + define_method(name) { host.send(name) } + define_method("#{name}?") { !!send(name) } + end + + host.class_eval <<-CODE + def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end + def #{name}?; !!#{name}; end + CODE + + if options[:global] + class_methods.class_eval do + define_method("#{name}=") { |val| host.send("#{name}=", val) } + end + else + class_methods.class_eval <<-CODE, __FILE__, __LINE__ + def #{name}=(val) + singleton_class.class_eval do + remove_possible_method(:#{name}) + define_method(:#{name}) { val } + end + end + CODE + end + + host.send(:attr_writer, name) if instance_writer? + end + end + end +end diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index c895968f77..9b12d9d281 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -10,11 +10,13 @@ module ActiveModel extend ActiveSupport::Concern included do - class_attribute :_accessible_attributes - class_attribute :_protected_attributes - class_attribute :_active_authorizer + extend ActiveModel::Configuration - class_attribute :_mass_assignment_sanitizer + config_attribute :_accessible_attributes + config_attribute :_protected_attributes + config_attribute :_active_authorizer + + config_attribute :_mass_assignment_sanitizer self.mass_assignment_sanitizer = :logger end @@ -56,7 +58,7 @@ module ActiveModel # You can specify your own sanitizer object eg. MySanitizer.new. # See ActiveModel::MassAssignmentSecurity::LoggerSanitizer for example implementation. # - # + # module ClassMethods # Attributes named in this macro are protected from mass-assignment # whenever attributes are sanitized before assignment. A role for the @@ -70,13 +72,13 @@ module ActiveModel # # class Customer # include ActiveModel::MassAssignmentSecurity - # + # # attr_accessor :name, :email, :logins_count - # + # # attr_protected :logins_count # # Suppose that admin can not change email for customer - # attr_protected :logins_count, :email, :as => :admin - # + # attr_protected :logins_count, :email, :as => :admin + # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) @@ -99,7 +101,7 @@ module ActiveModel # customer.name # => "David" # customer.email # => nil # customer.logins_count # => nil - # + # # customer.email = "c@d.com" # customer.email # => "c@d.com" # diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index c845440120..63ab8e7edc 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -10,8 +10,9 @@ module ActiveModel included do extend ActiveModel::Naming + extend ActiveModel::Configuration - class_attribute :include_root_in_json + config_attribute :include_root_in_json self.include_root_in_json = true end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 8ed392abca..1779efd526 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -42,9 +42,9 @@ module ActiveModel # module Validations extend ActiveSupport::Concern - include ActiveSupport::Callbacks included do + extend ActiveModel::Callbacks extend ActiveModel::Translation extend HelperMethods @@ -53,7 +53,8 @@ module ActiveModel attr_accessor :validation_context define_callbacks :validate, :scope => :name - class_attribute :_validators + extend ActiveModel::Configuration + config_attribute :_validators self._validators = Hash.new { |h,k| h[k] = [] } end diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb new file mode 100644 index 0000000000..a172fa26a3 --- /dev/null +++ b/activemodel/test/cases/configuration_test.rb @@ -0,0 +1,154 @@ +require 'cases/helper' + +class ConfigurationOnModuleTest < ActiveModel::TestCase + def setup + @mod = mod = Module.new do + extend ActiveSupport::Concern + extend ActiveModel::Configuration + + config_attribute :omg + self.omg = "default" + + config_attribute :wtf, global: true + self.wtf = "default" + + config_attribute :boolean + + config_attribute :lol, instance_writer: true + end + + @klass = Class.new do + include mod + end + + @subklass = Class.new(@klass) + end + + test "default" do + assert_equal "default", @mod.omg + assert_equal "default", @klass.omg + assert_equal "default", @klass.new.omg + end + + test "setting" do + @mod.omg = "lol" + assert_equal "lol", @mod.omg + end + + test "setting on class including the module" do + @klass.omg = "lol" + assert_equal "lol", @klass.omg + assert_equal "lol", @klass.new.omg + assert_equal "default", @mod.omg + end + + test "setting on subclass of class including the module" do + @subklass.omg = "lol" + assert_equal "lol", @subklass.omg + assert_equal "default", @klass.omg + assert_equal "default", @mod.omg + end + + test "setting on instance" do + assert !@klass.new.respond_to?(:omg=) + + @klass.lol = "lol" + obj = @klass.new + assert_equal "lol", obj.lol + obj.lol = "omg" + assert_equal "omg", obj.lol + assert_equal "lol", @klass.lol + assert_equal "lol", @klass.new.lol + obj.lol = false + assert !obj.lol? + end + + test "global attribute" do + assert_equal "default", @mod.wtf + assert_equal "default", @klass.wtf + + @mod.wtf = "wtf" + + assert_equal "wtf", @mod.wtf + assert_equal "wtf", @klass.wtf + + @klass.wtf = "lol" + + assert_equal "lol", @mod.wtf + assert_equal "lol", @klass.wtf + end + + test "boolean" do + assert_equal false, @mod.boolean? + assert_equal false, @klass.new.boolean? + @mod.boolean = true + assert_equal true, @mod.boolean? + assert_equal true, @klass.new.boolean? + end +end + +class ConfigurationOnClassTest < ActiveModel::TestCase + def setup + @klass = Class.new do + extend ActiveModel::Configuration + + config_attribute :omg + self.omg = "default" + + config_attribute :wtf, global: true + self.wtf = "default" + + config_attribute :omg2, instance_writer: true + config_attribute :wtf2, instance_writer: true, global: true + end + + @subklass = Class.new(@klass) + end + + test "defaults" do + assert_equal "default", @klass.omg + assert_equal "default", @klass.wtf + assert_equal "default", @subklass.omg + assert_equal "default", @subklass.wtf + end + + test "changing" do + @klass.omg = "lol" + assert_equal "lol", @klass.omg + assert_equal "lol", @subklass.omg + end + + test "changing in subclass" do + @subklass.omg = "lol" + assert_equal "lol", @subklass.omg + assert_equal "default", @klass.omg + end + + test "changing global" do + @klass.wtf = "wtf" + assert_equal "wtf", @klass.wtf + assert_equal "wtf", @subklass.wtf + + @subklass.wtf = "lol" + assert_equal "lol", @klass.wtf + assert_equal "lol", @subklass.wtf + end + + test "instance_writer" do + obj = @klass.new + + @klass.omg2 = "omg" + @klass.wtf2 = "wtf" + + assert_equal "omg", obj.omg2 + assert_equal "wtf", obj.wtf2 + + obj.omg2 = "lol" + obj.wtf2 = "lol" + + assert_equal "lol", obj.omg2 + assert_equal "lol", obj.wtf2 + assert_equal "omg", @klass.omg2 + assert_equal "lol", @klass.wtf2 + end +end diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 4d143146d5..360b1218e6 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -57,7 +57,6 @@ module ActiveRecord autoload :Base autoload :Callbacks - autoload :Configuration autoload :Core autoload :CounterCache autoload :DynamicMatchers diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index f61e016f46..40e4a97e73 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -13,7 +13,7 @@ module ActiveRecord raise "You cannot include Dirty after Timestamp" end - class_attribute :partial_updates + config_attribute :partial_updates self.partial_updates = true end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7d2d1db4b5..964c4123ef 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -5,7 +5,10 @@ module ActiveRecord ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] - Configuration.define :attribute_types_cached_by_default, ATTRIBUTE_TYPES_CACHED_BY_DEFAULT + included do + config_attribute :attribute_types_cached_by_default, :global => true + self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT + end module ClassMethods # +cache_attributes+ allows you to declare which converted attribute values should diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 2ffd91f796..0c8e4e4b9a 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. - class_attribute :serialized_attributes + config_attribute :serialized_attributes self.serialized_attributes = {} end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 5e5392441b..2f86e32f41 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -6,10 +6,11 @@ module ActiveRecord module TimeZoneConversion extend ActiveSupport::Concern - Configuration.define :time_zone_aware_attributes, false - included do - class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false + config_attribute :time_zone_aware_attributes, :global => true + self.time_zone_aware_attributes = false + + config_attribute :skip_time_zone_conversion_for_attributes self.skip_time_zone_conversion_for_attributes = [] end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6085df7d9f..fbdb50f6b3 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -328,7 +328,6 @@ 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/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index a175bf003c..8b2fc69b00 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -25,7 +25,7 @@ module ActiveRecord # Check out ActiveRecord::Transactions for more details about after_commit and # after_rollback. # - # Lastly an after_find and after_initialize callback is triggered for each object that + # Lastly an after_find and after_initialize callback is triggered for each object that # is found and instantiated by a finder, with after_initialize being triggered after new objects # are instantiated as well. # @@ -215,24 +215,48 @@ module ActiveRecord # instead of quietly returning +false+. # # == Debugging callbacks - # - # The callback chain is accessible via the _*_callbacks method on an object. ActiveModel Callbacks support + # + # The callback chain is accessible via the _*_callbacks method on an object. ActiveModel Callbacks support # :before, :after and :around as values for the kind property. The kind property # defines what part of the chain the callback runs in. - # - # To find all callbacks in the before_save callback chain: - # + # + # To find all callbacks in the before_save callback chain: + # # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) } - # + # # Returns an array of callback objects that form the before_save chain. - # + # # To further check if the before_save chain contains a proc defined as rest_when_dead use the filter property of the callback object: - # + # # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) - # + # # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. - # + # module Callbacks + # We can't define callbacks directly on ActiveRecord::Model because + # it is a module. So we queue up the definitions and execute them + # when ActiveRecord::Model is included. + module Register #:nodoc: + def self.extended(base) + base.config_attribute :_callbacks_register + base._callbacks_register = [] + end + + def self.setup(base) + base._callbacks_register.each do |item| + base.send(*item) + end + end + + def define_callbacks(*args) + self._callbacks_register << [:define_callbacks, *args] + end + + def define_model_callbacks(*args) + self._callbacks_register << [:define_model_callbacks, *args] + end + end + extend ActiveSupport::Concern CALLBACKS = [ @@ -242,8 +266,11 @@ module ActiveRecord :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback ] + module ClassMethods + include ActiveModel::Callbacks + end + included do - extend ActiveModel::Callbacks include ActiveModel::Validations::Callbacks define_model_callbacks :initialize, :find, :touch, :only => :after diff --git a/activerecord/lib/active_record/configuration.rb b/activerecord/lib/active_record/configuration.rb deleted file mode 100644 index d58ed82258..0000000000 --- a/activerecord/lib/active_record/configuration.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'active_support/concern' - -module ActiveRecord - # This module allows configuration options to be specified in a way such that - # ActiveRecord::Base and ActiveRecord::Model will have access to the same value, - # and will automatically get the appropriate readers and writers defined. - # - # In the future, we should probably move away from defining global config - # directly on ActiveRecord::Base / ActiveRecord::Model. - module Configuration #:nodoc: - extend ActiveSupport::Concern - - module ClassMethods - end - - def self.define(name, default = nil) - singleton_class.send(:attr_accessor, name) - - [self, ClassMethods].each do |klass| - klass.class_eval <<-CODE, __FILE__, __LINE__ - def #{name} - ActiveRecord::Configuration.#{name} - end - CODE - end - - ClassMethods.class_eval <<-CODE, __FILE__, __LINE__ - def #{name}=(val) - ActiveRecord::Configuration.#{name} = val - end - CODE - - send("#{name}=", default) unless default.nil? - end - end -end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 4f118e46a9..c29a0032bc 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -4,70 +4,75 @@ module ActiveRecord module Core extend ActiveSupport::Concern - ## - # :singleton-method: - # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, - # which is then passed on to any new database connections made and which can be retrieved on both - # a class and instance level by calling +logger+. - Configuration.define :logger - - ## - # :singleton-method: - # Contains the database configuration - as is typically stored in config/database.yml - - # as a Hash. - # - # For example, the following database.yml... - # - # development: - # adapter: sqlite3 - # database: db/development.sqlite3 - # - # production: - # adapter: sqlite3 - # database: db/production.sqlite3 - # - # ...would result in ActiveRecord::Base.configurations to look like this: - # - # { - # 'development' => { - # 'adapter' => 'sqlite3', - # 'database' => 'db/development.sqlite3' - # }, - # 'production' => { - # 'adapter' => 'sqlite3', - # 'database' => 'db/production.sqlite3' - # } - # } - Configuration.define :configurations, {} - - ## - # :singleton-method: - # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling - # dates and times from the database. This is set to :local by default. - Configuration.define :default_timezone, :local - - ## - # :singleton-method: - # Specifies the format to use when dumping the database schema with Rails' - # Rakefile. If :sql, the schema is dumped as (potentially database- - # specific) SQL statements. If :ruby, the schema is dumped as an - # ActiveRecord::Schema file which can be loaded into any database that - # supports migrations. Use :ruby if you want to have different database - # adapters for, e.g., your development and test environments. - Configuration.define :schema_format, :ruby - - ## - # :singleton-method: - # Specify whether or not to use timestamps for migration versions - Configuration.define :timestamped_migrations, true - included do ## # :singleton-method: - # The connection handler - class_attribute :connection_handler, :instance_writer => false + # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, + # which is then passed on to any new database connections made and which can be retrieved on both + # a class and instance level by calling +logger+. + config_attribute :logger, :global => true - initialize_generated_modules unless self == Base + ## + # :singleton-method: + # Contains the database configuration - as is typically stored in config/database.yml - + # as a Hash. + # + # For example, the following database.yml... + # + # development: + # adapter: sqlite3 + # database: db/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: db/production.sqlite3 + # + # ...would result in ActiveRecord::Base.configurations to look like this: + # + # { + # 'development' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/development.sqlite3' + # }, + # 'production' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/production.sqlite3' + # } + # } + config_attribute :configurations, :global => true + self.configurations = {} + + ## + # :singleton-method: + # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling + # dates and times from the database. This is set to :local by default. + config_attribute :default_timezone, :global => true + self.default_timezone = :local + + ## + # :singleton-method: + # Specifies the format to use when dumping the database schema with Rails' + # Rakefile. If :sql, the schema is dumped as (potentially database- + # specific) SQL statements. If :ruby, the schema is dumped as an + # ActiveRecord::Schema file which can be loaded into any database that + # supports migrations. Use :ruby if you want to have different database + # adapters for, e.g., your development and test environments. + config_attribute :schema_format, :global => true + self.schema_format = :ruby + + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + config_attribute :timestamped_migrations, :global => true + self.timestamped_migrations = true + + ## + # :singleton-method: + # The connection handler + config_attribute :connection_handler + self.connection_handler = ConnectionAdapters::ConnectionHandler.new + + # end module ClassMethods diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b5a67afd88..e502d7e52b 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -2,9 +2,11 @@ require 'active_support/core_ext/class/attribute' module ActiveRecord module Explain - # If a query takes longer than these many seconds we log its query plan - # automatically. nil disables this feature. - Configuration.define :auto_explain_threshold_in_seconds + def self.extended(base) + # If a query takes longer than these many seconds we log its query plan + # automatically. nil disables this feature. + base.config_attribute :auto_explain_threshold_in_seconds, :global => true + end # If auto explain is enabled, this method triggers EXPLAIN logging for the # queries triggered by the block if it takes more than the threshold as a diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index ec57151d40..0e92b7cf41 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # Determine whether to store the full constant name including namespace when using STI - class_attribute :store_full_sti_class + config_attribute :store_full_sti_class self.store_full_sti_class = true end @@ -88,7 +88,7 @@ module ActiveRecord # # Mainly for internal use. def active_record_super #:nodoc: - if self == Base || superclass && superclass < Model::Tag + if self == Base || superclass && superclass < Model superclass else Base @@ -100,7 +100,7 @@ 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) - unless klass < Model::Tag + unless klass < Model raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index b80d01db81..e643c0d437 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -48,7 +48,10 @@ module ActiveRecord module Optimistic extend ActiveSupport::Concern - Configuration.define :lock_optimistically, true + included do + config_attribute :lock_optimistically, :global => true + self.lock_optimistically = true + end def locking_enabled? #:nodoc: self.class.locking_enabled? diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb index f87be257db..86c4d18534 100644 --- a/activerecord/lib/active_record/model.rb +++ b/activerecord/lib/active_record/model.rb @@ -1,4 +1,5 @@ require 'active_support/deprecation' +require 'active_support/concern' module ActiveRecord # ActiveRecord::Model can be included into a class to add Active Record persistence. @@ -9,57 +10,61 @@ module ActiveRecord # end # 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: + module ClassMethods #:nodoc: + include ActiveSupport::Callbacks::ClassMethods + include ActiveModel::Naming + include QueryCache::ClassMethods + include ActiveSupport::Benchmarkable + include ActiveSupport::DescendantsTracker + + include Querying + include Translation + include DynamicMatchers + include CounterCache + include Explain end def self.included(base) - return if base < Tag + return if base.singleton_class < ClassMethods base.class_eval do - include Tag - - include Configuration - - include ActiveRecord::Persistence - extend ActiveModel::Naming - extend QueryCache::ClassMethods - extend ActiveSupport::Benchmarkable - extend ActiveSupport::DescendantsTracker - - extend Querying - include ReadonlyAttributes - include ModelSchema - extend Translation - include Inheritance - include Scoping - extend DynamicMatchers - include Sanitization - include Integration - include AttributeAssignment - include ActiveModel::Conversion - include Validations - extend CounterCache - include Locking::Optimistic, Locking::Pessimistic - include AttributeMethods - include Callbacks, ActiveModel::Observing, Timestamp - include Associations - include IdentityMap - include ActiveModel::SecurePassword - extend Explain - - # AutosaveAssociation needs to be included before Transactions, because we want - # #save_with_autosave_associations to be wrapped inside a transaction. - include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization, Store - - include Core - - self.connection_handler = Base.connection_handler + extend ClassMethods + Callbacks::Register.setup(self) + initialize_generated_modules unless self == Base end end + extend ActiveModel::Configuration + extend ActiveModel::Callbacks + extend ActiveModel::MassAssignmentSecurity::ClassMethods + extend ActiveModel::AttributeMethods::ClassMethods + extend Callbacks::Register + extend Explain + + def self.extend(*modules) + ClassMethods.send(:include, *modules) + end + + include Persistence + include ReadonlyAttributes + include ModelSchema + include Inheritance + include Scoping + include Sanitization + include Integration + include AttributeAssignment + include ActiveModel::Conversion + include Validations + include Locking::Optimistic, Locking::Pessimistic + include AttributeMethods + include Callbacks, ActiveModel::Observing, Timestamp + include Associations + include IdentityMap + include ActiveModel::SecurePassword + include AutosaveAssociation, NestedAttributes + include Aggregations, Transactions, Reflection, Serialization, Store + include Core + module DeprecationProxy #:nodoc: class << self instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$|^instance_eval$/ } diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index adf85c6436..d9d18a9752 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -5,16 +5,16 @@ module ActiveRecord module ModelSchema extend ActiveSupport::Concern - ## - # :singleton-method: - # Accessor for the prefix type that will be prepended to every primary key column name. - # The options are :table_name and :table_name_with_underscore. If the first is specified, - # the Product class will look for "productid" instead of "id" as the primary column. If the - # latter is specified, the Product class will look for "product_id" instead of "id". Remember - # that this is a global setting for all Active Records. - Configuration.define :primary_key_prefix_type - included do + ## + # :singleton-method: + # Accessor for the prefix type that will be prepended to every primary key column name. + # The options are :table_name and :table_name_with_underscore. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + config_attribute :primary_key_prefix_type, :global => true + ## # :singleton-method: # Accessor for the name of the prefix string to prepend to every table name. So if set @@ -25,14 +25,14 @@ module ActiveRecord # If you are organising your models within modules you can add a prefix to the models within # a namespace by defining a singleton method in the parent module called table_name_prefix which # returns your chosen prefix. - class_attribute :table_name_prefix, :instance_writer => false + config_attribute :table_name_prefix self.table_name_prefix = "" ## # :singleton-method: # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", # "people_basecamp"). By default, the suffix is the empty string. - class_attribute :table_name_suffix, :instance_writer => false + config_attribute :table_name_suffix self.table_name_suffix = "" ## @@ -40,7 +40,7 @@ module ActiveRecord # Indicates whether table names should be the pluralized versions of the corresponding class names. # If true, the default table name for a Product class will be +products+. If false, it would just be +product+. # See table_name for the full rules on table/class naming. This is true, by default. - class_attribute :pluralize_table_names, :instance_writer => false + config_attribute :pluralize_table_names self.pluralize_table_names = true end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d2065d701f..a8ee43b598 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -12,7 +12,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :nested_attributes_options, :instance_writer => false + config_attribute :nested_attributes_options self.nested_attributes_options = {} end diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index bf37ab5f14..836b15e2ce 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -6,7 +6,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :_attr_readonly, :instance_writer => false + config_attribute :_attr_readonly self._attr_readonly = [] end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 794a0526fb..60b73e9fe5 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -7,7 +7,8 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :reflections + extend ActiveModel::Configuration + config_attribute :reflections self.reflections = {} end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 6b5070808a..5f05d146f2 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -7,7 +7,7 @@ module ActiveRecord included do # Stores the default scope for the class - class_attribute :default_scopes, :instance_writer => false + config_attribute :default_scopes self.default_scopes = [] end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 0c760e9850..c717fdea47 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -33,7 +33,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :record_timestamps + config_attribute :record_timestamps, :instance_writer => true self.record_timestamps = true end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 375c207d20..0df9ffc0c5 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -18,7 +18,6 @@ module ActiveRecord def self.active_record_super; Base; end def self.base_class; self; end - include ActiveRecord::Configuration include ActiveRecord::AttributeMethods def self.column_names diff --git a/activerecord/test/cases/configuration_test.rb b/activerecord/test/cases/configuration_test.rb deleted file mode 100644 index 872f1fc33b..0000000000 --- a/activerecord/test/cases/configuration_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'cases/helper' - -class ConfigurationTest < ActiveRecord::TestCase - def test_configuration - @klass = Class.new do - include ActiveRecord::Configuration - end - - ActiveRecord::Configuration.define :omg - - ActiveRecord::Configuration.omg = "omg" - - assert_equal "omg", @klass.new.omg - assert !@klass.new.respond_to?(:omg=) - assert_equal "omg", @klass.omg - - @klass.omg = "wtf" - - assert_equal "wtf", @klass.omg - assert_equal "wtf", @klass.new.omg - ensure - ActiveRecord::Configuration.send(:undef_method, :omg) - ActiveRecord::Configuration::ClassMethods.send(:undef_method, :omg) - ActiveRecord::Configuration::ClassMethods.send(:undef_method, :omg=) - end -end