diff options
Diffstat (limited to 'activerecord/lib/active_record')
30 files changed, 734 insertions, 820 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 861dda618a..7887d59aad 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -230,13 +230,8 @@ module ActiveRecord end def build_record(attributes, options) - attributes = (attributes || {}).reverse_merge(creation_attributes) - reflection.build_association(attributes, options) do |record| - record.assign_attributes( - create_scope.except(*record.changed), - :without_protection => true - ) + record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 650fa3fc42..44b0956e4e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -16,7 +16,6 @@ module ActiveRecord include TimeZoneConversion include Dirty include Serialization - include DeprecatedUnderscoreRead # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). @@ -35,48 +34,26 @@ module ActiveRecord # accessors, mutators and query methods. def define_attribute_methods return if attribute_methods_generated? - - if base_class == self - super(column_names) - @attribute_methods_generated = true - else - base_class.define_attribute_methods - end + superclass.define_attribute_methods unless self == base_class + super(column_names) + @attribute_methods_generated = true end def attribute_methods_generated? - if base_class == self - @attribute_methods_generated ||= false - else - base_class.attribute_methods_generated? - end - end - - def generated_attribute_methods - @generated_attribute_methods ||= (base_class == self ? super : base_class.generated_attribute_methods) + @attribute_methods_generated ||= false end + # We will define the methods as instance methods, but will call them as singleton + # methods. This allows us to use method_defined? to check if the method exists, + # which is fast and won't give any false positives from the ancestors (because + # there are no ancestors). def generated_external_attribute_methods - @generated_external_attribute_methods ||= begin - if base_class == self - # We will define the methods as instance methods, but will call them as singleton - # methods. This allows us to use method_defined? to check if the method exists, - # which is fast and won't give any false positives from the ancestors (because - # there are no ancestors). - Module.new { extend self } - else - base_class.generated_external_attribute_methods - end - end + @generated_external_attribute_methods ||= Module.new { extend self } end def undefine_attribute_methods - if base_class == self - super - @attribute_methods_generated = false - else - base_class.undefine_attribute_methods - end + super if attribute_methods_generated? + @attribute_methods_generated = false end def instance_method_already_implemented?(method_name) @@ -84,19 +61,31 @@ module ActiveRecord raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" end - super + if active_record_super == Base + super + else + # If B < A and A defines its own attribute method, then we don't want to overwrite that. + defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods) + defined && !ActiveRecord::Base.method_defined?(method_name) || super + end end # A method name is 'dangerous' if it is already defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) - def dangerous_attribute_method?(method_name) - active_record = ActiveRecord::Base - superclass = ActiveRecord::Base.superclass - - (active_record.method_defined?(method_name) || - active_record.private_method_defined?(method_name)) && - !superclass.method_defined?(method_name) && - !superclass.private_method_defined?(method_name) + def dangerous_attribute_method?(name) + method_defined_within?(name, Base) + end + + def method_defined_within?(name, klass, sup = klass.superclass) + if klass.method_defined?(name) || klass.private_method_defined?(name) + if sup.method_defined?(name) || sup.private_method_defined?(name) + klass.instance_method(name).owner != sup.instance_method(name).owner + else + true + end + else + false + end end def attribute_method?(attribute) diff --git a/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb b/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb deleted file mode 100644 index 0eb0db65b1..0000000000 --- a/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'active_support/concern' -require 'active_support/deprecation' - -module ActiveRecord - module AttributeMethods - module DeprecatedUnderscoreRead - extend ActiveSupport::Concern - - included do - attribute_method_prefix "_" - end - - module ClassMethods - protected - - def define_method__attribute(attr_name) - # Do nothing, let it hit method missing instead. - end - end - - protected - - def _attribute(attr_name) - ActiveSupport::Deprecation.warn( - "You have called '_#{attr_name}'. This is deprecated. Please use " \ - "either '#{attr_name}' or read_attribute('#{attr_name}')." - ) - read_attribute(attr_name) - end - end - end -end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 5d37088d98..a7785f8786 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -80,10 +80,6 @@ module ActiveRecord end end - def original_primary_key #:nodoc: - deprecated_original_property_getter :primary_key - end - # Sets the name of the primary key column. # # class Project < ActiveRecord::Base @@ -103,11 +99,6 @@ module ActiveRecord @primary_key = value && value.to_s @quoted_primary_key = nil end - - def set_primary_key(value = nil, &block) #:nodoc: - deprecated_property_setter :primary_key, value, block - @quoted_primary_key = nil - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 77bf9d0905..7d2d1db4b5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -5,10 +5,7 @@ module ActiveRecord ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] - included do - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false - self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - end + Configuration.define :attribute_types_cached_by_default, ATTRIBUTE_TYPES_CACHED_BY_DEFAULT module ClassMethods # +cache_attributes+ allows you to declare which converted attribute values should @@ -30,15 +27,33 @@ module ActiveRecord end def undefine_attribute_methods - if base_class == self - generated_external_attribute_methods.module_eval do - instance_methods.each { |m| undef_method(m) } - end + generated_external_attribute_methods.module_eval do + instance_methods.each { |m| undef_method(m) } end super end + def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc: + return unless attr_name + attr_name = attr_name.to_s + + if generated_external_attribute_methods.method_defined?(attr_name) + if attributes.has_key?(attr_name) || attr_name == 'id' + generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name) + end + elsif !attribute_methods_generated? + # If we haven't generated the caster methods yet, do that and + # then try again + define_attribute_methods + type_cast_attribute(attr_name, attributes, cache) + else + # If we get here, the attribute has no associated DB column, so + # just return it verbatim. + attributes[attr_name] + end + end + protected # We want to generate the methods via module_eval rather than define_method, # because define_method is slower on dispatch and uses more memory (because it @@ -105,25 +120,7 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - return unless attr_name - - attr_name = attr_name.to_s - methods = self.class.generated_external_attribute_methods - - if methods.method_defined?(attr_name) - if @attributes.has_key?(attr_name) || attr_name == 'id' - methods.send(attr_name, @attributes[attr_name], @attributes, @attributes_cache, attr_name) - end - elsif !self.class.attribute_methods_generated? - # If we haven't generated the caster methods yet, do that and - # then try again - self.class.define_attribute_methods - read_attribute(attr_name) - else - # If we get here, the attribute has no associated DB column, so - # just return it verbatim. - @attributes[attr_name] - end + self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache) end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 0a4432506f..2ffd91f796 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -58,6 +58,18 @@ module ActiveRecord self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) end + def initialize_attributes(attributes) #:nodoc: + super + + serialized_attributes.each do |key, coder| + if attributes.key?(key) + attributes[key] = Attribute.new(coder, attributes[key], :serialized) + end + end + + attributes + end + private def attribute_cast_code(attr_name) @@ -69,14 +81,6 @@ module ActiveRecord end end - def set_serialized_attributes - self.class.serialized_attributes.each do |key, coder| - if @attributes.key?(key) - @attributes[key] = Attribute.new(coder, @attributes[key], :serialized) - end - end - end - def type_cast_attribute_for_write(column, value) if column && coder = self.class.serialized_attributes[column.name] Attribute.new(coder, value, :unserialized) 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 17cf34cdf6..5e5392441b 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,9 @@ module ActiveRecord module TimeZoneConversion extend ActiveSupport::Concern - included do - cattr_accessor :time_zone_aware_attributes, :instance_writer => false - self.time_zone_aware_attributes = false + Configuration.define :time_zone_aware_attributes, false + included do class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false 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 432a40ea54..6085df7d9f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -327,386 +327,10 @@ module ActiveRecord #:nodoc: # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all # instances in the current object space. class Base - ## - # :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+. - cattr_accessor :logger, :instance_writer => false - - ## - # :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' - # } - # } - cattr_accessor :configurations, :instance_writer => false - @@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. - cattr_accessor :default_timezone, :instance_writer => false - @@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. - cattr_accessor :schema_format , :instance_writer => false - @@schema_format = :ruby - - ## - # :singleton-method: - # Specify whether or not to use timestamps for migration versions - cattr_accessor :timestamped_migrations , :instance_writer => false - @@timestamped_migrations = true - - class << self # Class methods - def inherited(child_class) #:nodoc: - # force attribute methods to be higher in inheritance hierarchy than other generated methods - child_class.generated_attribute_methods - child_class.generated_feature_methods - super - end - - def generated_feature_methods - @generated_feature_methods ||= begin - mod = const_set(:GeneratedFeatureMethods, Module.new) - include mod - mod - end - end - - # Returns a string like 'Post(id:integer, title:string, body:text)' - def inspect - if self == Base - super - elsif abstract_class? - "#{super}(abstract)" - elsif table_exists? - attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' - "#{super}(#{attr_list})" - else - "#{super}(Table doesn't exist)" - end - end - - # Overwrite the default class equality method to provide support for association proxies. - def ===(object) - object.is_a?(self) - end - - def arel_table - @arel_table ||= Arel::Table.new(table_name, arel_engine) - end - - def arel_engine - @arel_engine ||= begin - if self == ActiveRecord::Base - ActiveRecord::Base - else - connection_handler.connection_pools[name] ? self : superclass.arel_engine - end - end - end - - private - - def relation #:nodoc: - @relation ||= Relation.new(self, arel_table) - - if finder_needs_type_condition? - @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) - else - @relation - end - end - end - - public - # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with - # attributes but not yet saved (pass a hash with key names matching the associated table column names). - # In both instances, valid attribute keys are determined by the column names of the associated table -- - # hence you can't have attributes that aren't part of the table columns. - # - # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options - # in the +options+ parameter. - # - # ==== Examples - # # Instantiates a single new object - # User.new(:first_name => 'Jamie') - # - # # Instantiates a single new object using the :admin mass-assignment security role - # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) - # - # # Instantiates a single new object bypassing mass-assignment security - # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) - def initialize(attributes = nil, options = {}) - @attributes = attributes_from_column_definition - @association_cache = {} - @aggregation_cache = {} - @attributes_cache = {} - @new_record = true - @readonly = false - @destroyed = false - @marked_for_destruction = false - @previously_changed = {} - @changed_attributes = {} - @relation = nil - - ensure_proper_type - set_serialized_attributes - - populate_with_current_scope_attributes - - assign_attributes(attributes, options) if attributes - - yield self if block_given? - run_callbacks :initialize - end - - # Initialize an empty model object from +coder+. +coder+ must contain - # the attributes necessary for initializing an empty model object. For - # example: - # - # class Post < ActiveRecord::Base - # end - # - # post = Post.allocate - # post.init_with('attributes' => { 'title' => 'hello world' }) - # post.title # => 'hello world' - def init_with(coder) - @attributes = coder['attributes'] - @relation = nil - - set_serialized_attributes - - @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {} - @association_cache = {} - @aggregation_cache = {} - @readonly = @destroyed = @marked_for_destruction = false - @new_record = false - run_callbacks :find - run_callbacks :initialize - - self - end - - # Duped objects have no id assigned and are treated as new records. Note - # that this is a "shallow" copy as it copies the object's attributes - # only, not its associations. The extent of a "deep" copy is application - # specific and is therefore left to the application to implement according - # to its need. - # The dup method does not preserve the timestamps (created|updated)_(at|on). - def initialize_dup(other) - cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) - cloned_attributes.delete(self.class.primary_key) - - @attributes = cloned_attributes - - _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) - - @changed_attributes = {} - attributes_from_column_definition.each do |attr, orig_value| - @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) - end - - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @new_record = true - - ensure_proper_type - populate_with_current_scope_attributes - super - end - - # Backport dup from 1.9 so that initialize_dup() gets called - unless Object.respond_to?(:initialize_dup) - def dup # :nodoc: - copy = super - copy.initialize_dup(self) - copy - end - end - - # Populate +coder+ with attributes about this record that should be - # serialized. The structure of +coder+ defined in this method is - # guaranteed to match the structure of +coder+ passed to the +init_with+ - # method. - # - # Example: - # - # class Post < ActiveRecord::Base - # end - # coder = {} - # Post.new.encode_with(coder) - # coder # => { 'id' => nil, ... } - def encode_with(coder) - coder['attributes'] = attributes - end - - # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ - # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. - # - # Note that new records are different from any other record by definition, unless the - # other record is the receiver itself. Besides, if you fetch existing records with - # +select+ and leave the ID out, you're on your own, this predicate will return false. - # - # Note also that destroying a record preserves its ID in the model instance, so deleted - # models are still comparable. - def ==(comparison_object) - super || - comparison_object.instance_of?(self.class) && - id.present? && - comparison_object.id == id - end - alias :eql? :== - - # Delegates to id in order to allow two records of the same type and id to work with something like: - # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] - def hash - id.hash - end - - # Freeze the attributes hash such that associations are still accessible, even on destroyed records. - def freeze - @attributes.freeze; self - end - - # Returns +true+ if the attributes hash has been frozen. - def frozen? - @attributes.frozen? - end - - # Allows sort on objects - def <=>(other_object) - if other_object.is_a?(self.class) - self.to_key <=> other_object.to_key - else - nil - end - end - - # Returns +true+ if the record is read only. Records loaded through joins with piggy-back - # attributes will be marked as read only since they cannot be saved. - def readonly? - @readonly - end - - # Marks this record as read only. - def readonly! - @readonly = true - end - - # Returns the contents of the record as a nicely formatted string. - def inspect - inspection = if @attributes - self.class.column_names.collect { |name| - if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" - end - }.compact.join(", ") - else - "not initialized" - end - "#<#{self.class} #{inspection}>" - end - - # Hackery to accomodate Syck. Remove for 4.0. - def to_yaml(opts = {}) #:nodoc: - if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? - super - else - coder = {} - encode_with(coder) - YAML.quick_emit(self, opts) do |out| - out.map(taguri, to_yaml_style) do |map| - coder.each { |k, v| map.add(k, v) } - end - end - end - end - - # Hackery to accomodate Syck. Remove for 4.0. - def yaml_initialize(tag, coder) #:nodoc: - init_with(coder) - end - - private - - # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements - # of the array, and then rescues from the possible NoMethodError. If those elements are - # ActiveRecord::Base's, then this triggers the various method_missing's that we have, - # which significantly impacts upon performance. - # - # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. - # - # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ - def to_ary # :nodoc: - nil - end - - 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 ActiveRecord::Model + self.connection_handler = ConnectionAdapters::ConnectionHandler.new 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::DeprecationProxy) diff --git a/activerecord/lib/active_record/configuration.rb b/activerecord/lib/active_record/configuration.rb new file mode 100644 index 0000000000..d58ed82258 --- /dev/null +++ b/activerecord/lib/active_record/configuration.rb @@ -0,0 +1,36 @@ +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/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 698da34d26..5749d45a18 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -162,34 +162,6 @@ module ActiveRecord end end - def columns - with_connection do |c| - c.schema_cache.columns - end - end - deprecate :columns - - def columns_hash - with_connection do |c| - c.schema_cache.columns_hash - end - end - deprecate :columns_hash - - def primary_keys - with_connection do |c| - c.schema_cache.primary_keys - end - end - deprecate :primary_keys - - def clear_cache! - with_connection do |c| - c.schema_cache.clear! - end - end - deprecate :clear_cache! - # Return any checked-out connections back to the pool by threads that # are no longer alive. def clear_stale_cached_connections! @@ -399,7 +371,7 @@ 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 + retrieve_connection_pool klass.active_record_super 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..901d8422f2 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] @@ -224,52 +224,48 @@ module ActiveRecord @statements.clear end - if "<3".respond_to?(:encode) - # Taken from here: - # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb - # Author: TOMITA Masahiro <tommy@tmtm.org> - ENCODINGS = { - "armscii8" => nil, - "ascii" => Encoding::US_ASCII, - "big5" => Encoding::Big5, - "binary" => Encoding::ASCII_8BIT, - "cp1250" => Encoding::Windows_1250, - "cp1251" => Encoding::Windows_1251, - "cp1256" => Encoding::Windows_1256, - "cp1257" => Encoding::Windows_1257, - "cp850" => Encoding::CP850, - "cp852" => Encoding::CP852, - "cp866" => Encoding::IBM866, - "cp932" => Encoding::Windows_31J, - "dec8" => nil, - "eucjpms" => Encoding::EucJP_ms, - "euckr" => Encoding::EUC_KR, - "gb2312" => Encoding::EUC_CN, - "gbk" => Encoding::GBK, - "geostd8" => nil, - "greek" => Encoding::ISO_8859_7, - "hebrew" => Encoding::ISO_8859_8, - "hp8" => nil, - "keybcs2" => nil, - "koi8r" => Encoding::KOI8_R, - "koi8u" => Encoding::KOI8_U, - "latin1" => Encoding::ISO_8859_1, - "latin2" => Encoding::ISO_8859_2, - "latin5" => Encoding::ISO_8859_9, - "latin7" => Encoding::ISO_8859_13, - "macce" => Encoding::MacCentEuro, - "macroman" => Encoding::MacRoman, - "sjis" => Encoding::SHIFT_JIS, - "swe7" => nil, - "tis620" => Encoding::TIS_620, - "ucs2" => Encoding::UTF_16BE, - "ujis" => Encoding::EucJP_ms, - "utf8" => Encoding::UTF_8, - "utf8mb4" => Encoding::UTF_8, - } - else - ENCODINGS = Hash.new { |h,k| h[k] = k } - end + # Taken from here: + # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb + # Author: TOMITA Masahiro <tommy@tmtm.org> + ENCODINGS = { + "armscii8" => nil, + "ascii" => Encoding::US_ASCII, + "big5" => Encoding::Big5, + "binary" => Encoding::ASCII_8BIT, + "cp1250" => Encoding::Windows_1250, + "cp1251" => Encoding::Windows_1251, + "cp1256" => Encoding::Windows_1256, + "cp1257" => Encoding::Windows_1257, + "cp850" => Encoding::CP850, + "cp852" => Encoding::CP852, + "cp866" => Encoding::IBM866, + "cp932" => Encoding::Windows_31J, + "dec8" => nil, + "eucjpms" => Encoding::EucJP_ms, + "euckr" => Encoding::EUC_KR, + "gb2312" => Encoding::EUC_CN, + "gbk" => Encoding::GBK, + "geostd8" => nil, + "greek" => Encoding::ISO_8859_7, + "hebrew" => Encoding::ISO_8859_8, + "hp8" => nil, + "keybcs2" => nil, + "koi8r" => Encoding::KOI8_R, + "koi8u" => Encoding::KOI8_U, + "latin1" => Encoding::ISO_8859_1, + "latin2" => Encoding::ISO_8859_2, + "latin5" => Encoding::ISO_8859_9, + "latin7" => Encoding::ISO_8859_13, + "macce" => Encoding::MacCentEuro, + "macroman" => Encoding::MacRoman, + "sjis" => Encoding::SHIFT_JIS, + "swe7" => nil, + "tis620" => Encoding::TIS_620, + "ucs2" => Encoding::UTF_16BE, + "ujis" => Encoding::EucJP_ms, + "utf8" => Encoding::UTF_8, + "utf8mb4" => Encoding::UTF_8, + } # Get the client encoding for this database def client_encoding 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/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 55818b3fbf..69750a911d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -16,7 +16,7 @@ module ActiveRecord end def binary_to_string(value) - if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT + if value.encoding != Encoding::ASCII_8BIT value = value.force_encoding(Encoding::ASCII_8BIT) end @@ -201,25 +201,17 @@ module ActiveRecord end end - if "<3".encoding_aware? - def type_cast(value, column) # :nodoc: - return value.to_f if BigDecimal === value - return super unless String === value - return super unless column && value + def type_cast(value, column) # :nodoc: + return value.to_f if BigDecimal === value + return super unless String === value + return super unless column && value - value = super - if column.type == :string && value.encoding == Encoding::ASCII_8BIT - @logger.error "Binary data inserted for `string` type on column `#{column.name}`" - value.encode! 'utf-8' - end - value - end - else - def type_cast(value, column) # :nodoc: - return super unless BigDecimal === value - - value.to_f + value = super + if column.type == :string && value.encoding == Encoding::ASCII_8BIT + @logger.error "Binary data inserted for `string` type on column `#{column.name}`" + value.encode! 'utf-8' end + value end # DATABASE STATEMENTS ====================================== diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb new file mode 100644 index 0000000000..4f118e46a9 --- /dev/null +++ b/activerecord/lib/active_record/core.rb @@ -0,0 +1,350 @@ +require 'active_support/concern' + +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 + + initialize_generated_modules unless self == Base + end + + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules + # force attribute methods to be higher in inheritance hierarchy than other generated methods + generated_attribute_methods + generated_feature_methods + end + + def generated_feature_methods + @generated_feature_methods ||= begin + mod = const_set(:GeneratedFeatureMethods, Module.new) + include mod + mod + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect + if self == Base + super + elsif abstract_class? + "#{super}(abstract)" + elsif table_exists? + attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Overwrite the default class equality method to provide support for association proxies. + def ===(object) + object.is_a?(self) + end + + def arel_table + @arel_table ||= Arel::Table.new(table_name, arel_engine) + end + + def arel_engine + @arel_engine ||= begin + if self == ActiveRecord::Base + ActiveRecord::Base + else + connection_handler.connection_pools[name] ? self : active_record_super.arel_engine + end + end + end + + private + + def relation #:nodoc: + @relation ||= Relation.new(self, arel_table) + + if finder_needs_type_condition? + @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + else + @relation + end + end + end + + # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with + # attributes but not yet saved (pass a hash with key names matching the associated table column names). + # In both instances, valid attribute keys are determined by the column names of the associated table -- + # hence you can't have attributes that aren't part of the table columns. + # + # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # + # ==== Examples + # # Instantiates a single new object + # User.new(:first_name => 'Jamie') + # + # # Instantiates a single new object using the :admin mass-assignment security role + # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Instantiates a single new object bypassing mass-assignment security + # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + def initialize(attributes = nil, options = {}) + @attributes = self.class.initialize_attributes(self.class.column_defaults.dup) + @association_cache = {} + @aggregation_cache = {} + @attributes_cache = {} + @new_record = true + @readonly = false + @destroyed = false + @marked_for_destruction = false + @previously_changed = {} + @changed_attributes = {} + @relation = nil + + ensure_proper_type + + populate_with_current_scope_attributes + + assign_attributes(attributes, options) if attributes + + yield self if block_given? + run_callbacks :initialize + end + + # Initialize an empty model object from +coder+. +coder+ must contain + # the attributes necessary for initializing an empty model object. For + # example: + # + # class Post < ActiveRecord::Base + # end + # + # post = Post.allocate + # post.init_with('attributes' => { 'title' => 'hello world' }) + # post.title # => 'hello world' + def init_with(coder) + @attributes = self.class.initialize_attributes(coder['attributes']) + @relation = nil + + @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {} + @association_cache = {} + @aggregation_cache = {} + @readonly = @destroyed = @marked_for_destruction = false + @new_record = false + run_callbacks :find + run_callbacks :initialize + + self + end + + # Duped objects have no id assigned and are treated as new records. Note + # that this is a "shallow" copy as it copies the object's attributes + # only, not its associations. The extent of a "deep" copy is application + # specific and is therefore left to the application to implement according + # to its need. + # The dup method does not preserve the timestamps (created|updated)_(at|on). + def initialize_dup(other) + cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) + cloned_attributes.delete(self.class.primary_key) + + @attributes = cloned_attributes + + _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) + + @changed_attributes = {} + self.class.column_defaults.each do |attr, orig_value| + @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) + end + + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @new_record = true + + ensure_proper_type + populate_with_current_scope_attributes + super + end + + # Populate +coder+ with attributes about this record that should be + # serialized. The structure of +coder+ defined in this method is + # guaranteed to match the structure of +coder+ passed to the +init_with+ + # method. + # + # Example: + # + # class Post < ActiveRecord::Base + # end + # coder = {} + # Post.new.encode_with(coder) + # coder # => { 'id' => nil, ... } + def encode_with(coder) + coder['attributes'] = attributes + end + + # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ + # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. + # + # Note that new records are different from any other record by definition, unless the + # other record is the receiver itself. Besides, if you fetch existing records with + # +select+ and leave the ID out, you're on your own, this predicate will return false. + # + # Note also that destroying a record preserves its ID in the model instance, so deleted + # models are still comparable. + def ==(comparison_object) + super || + comparison_object.instance_of?(self.class) && + id.present? && + comparison_object.id == id + end + alias :eql? :== + + # Delegates to id in order to allow two records of the same type and id to work with something like: + # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] + def hash + id.hash + end + + # Freeze the attributes hash such that associations are still accessible, even on destroyed records. + def freeze + @attributes.freeze; self + end + + # Returns +true+ if the attributes hash has been frozen. + def frozen? + @attributes.frozen? + end + + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + self.to_key <=> other_object.to_key + else + nil + end + end + + # Returns +true+ if the record is read only. Records loaded through joins with piggy-back + # attributes will be marked as read only since they cannot be saved. + def readonly? + @readonly + end + + # Marks this record as read only. + def readonly! + @readonly = true + end + + # Returns the contents of the record as a nicely formatted string. + def inspect + inspection = if @attributes + self.class.column_names.collect { |name| + if has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + }.compact.join(", ") + else + "not initialized" + end + "#<#{self.class} #{inspection}>" + end + + # Hackery to accomodate Syck. Remove for 4.0. + def to_yaml(opts = {}) #:nodoc: + if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? + super + else + coder = {} + encode_with(coder) + YAML.quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + coder.each { |k, v| map.add(k, v) } + end + end + end + end + + # Hackery to accomodate Syck. Remove for 4.0. + def yaml_initialize(tag, coder) #:nodoc: + init_with(coder) + end + + private + + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements + # of the array, and then rescues from the possible NoMethodError. If those elements are + # ActiveRecord::Base's, then this triggers the various method_missing's that we have, + # which significantly impacts upon performance. + # + # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ + def to_ary # :nodoc: + nil + end + end +end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b64390250d..b5a67afd88 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -2,14 +2,9 @@ require 'active_support/core_ext/class/attribute' module ActiveRecord module Explain - def self.extended(base) - base.class_eval do - # If a query takes longer than these many seconds we log its query plan - # automatically. nil disables this feature. - class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false - self.auto_explain_threshold_in_seconds = nil - end - end + # 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 # 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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index da72d1718e..65c7f3afbb 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -559,7 +559,7 @@ module ActiveRecord rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash - if model_class && model_class < ActiveRecord::Base + if model_class && model_class < ActiveRecord::Model # fill in timestamp columns if they aren't specified and the model is set to record_timestamps if model_class.record_timestamps timestamp_column_names.each do |name| diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index b15b5a8133..680d9ffea0 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -111,13 +111,12 @@ module ActiveRecord # model object. def reinit_with(coder) @attributes_cache = {} - dirty = @changed_attributes.keys - @attributes.update(coder['attributes'].except(*dirty)) + dirty = @changed_attributes.keys + attributes = self.class.initialize_attributes(coder['attributes'].except(*dirty)) + @attributes.update(attributes) @changed_attributes.update(coder['attributes'].slice(*dirty)) @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]} - set_serialized_attributes - run_callbacks :find self diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index de9461982a..ec57151d40 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -13,10 +13,12 @@ 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? - superclass.descends_from_active_record? + sup = active_record_super + + if sup.abstract_class? + sup.descends_from_active_record? else - superclass == Base || !columns_hash.include?(inheritance_column) + sup == Base || !columns_hash.include?(inheritance_column) end end @@ -79,17 +81,34 @@ module ActiveRecord instance end + # If this class includes ActiveRecord::Model then it won't have a + # superclass. So this provides a way to get to the 'root' (ActiveRecord::Base), + # through inheritance hierarchy, ending in Base, whether or not that is + # actually an ancestor of the class. + # + # Mainly for internal use. + def active_record_super #:nodoc: + if self == Base || superclass && superclass < Model::Tag + superclass + else + Base + end + end + protected # 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 + + sup = klass.active_record_super + if klass == Base || sup == Base || sup.abstract_class? + klass else - class_of_active_record_descendant(klass.superclass) + class_of_active_record_descendant(sup) end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index ce0a165660..b80d01db81 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -48,10 +48,7 @@ module ActiveRecord module Optimistic extend ActiveSupport::Concern - included do - cattr_accessor :lock_optimistically, :instance_writer => false - self.lock_optimistically = true - end + Configuration.define :lock_optimistically, true def locking_enabled? #:nodoc: self.class.locking_enabled? @@ -64,21 +61,6 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end - def attributes_from_column_definition - result = self.class.column_defaults.dup - - # If the locking column has no default value set, - # start the lock version at zero. Note we can't use - # <tt>locking_enabled?</tt> at this point as - # <tt>@attributes</tt> may not have been initialized yet. - - if result.key?(self.class.locking_column) && lock_optimistically - result[self.class.locking_column] ||= 0 - end - - result - end - def update(attribute_names = @attributes.keys) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? @@ -144,26 +126,18 @@ module ActiveRecord lock_optimistically && columns_hash[locking_column] end + # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) @original_locking_column = @locking_column if defined?(@locking_column) @locking_column = value.to_s end - # Set the column to use for optimistic locking. Defaults to +lock_version+. - def set_locking_column(value = nil, &block) - deprecated_property_setter :locking_column, value, block - end - # The version column used for optimistic locking. Defaults to +lock_version+. def locking_column reset_locking_column unless defined?(@locking_column) @locking_column end - def original_locking_column #:nodoc: - deprecated_original_property_getter :locking_column - end - # Quote the column name used for optimistic locking. def quoted_locking_column connection.quote_column_name(locking_column) @@ -180,6 +154,18 @@ module ActiveRecord counters = counters.merge(locking_column => 1) if locking_enabled? super end + + # If the locking column has no default value set, + # start the lock version at zero. Note we can't use + # <tt>locking_enabled?</tt> at this point as + # <tt>@attributes</tt> may not have been initialized yet. + def initialize_attributes(attributes) #:nodoc: + if attributes.key?(locking_column) && lock_optimistically + attributes[locking_column] ||= 0 + end + + attributes + end end end end diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb new file mode 100644 index 0000000000..f87be257db --- /dev/null +++ b/activerecord/lib/active_record/model.rb @@ -0,0 +1,89 @@ +require 'active_support/deprecation' + +module ActiveRecord + # <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence. + # This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example: + # + # class Post + # include ActiveRecord::Model + # 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: + end + + def self.included(base) + return if base < Tag + + 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 + end + end + + module DeprecationProxy #:nodoc: + class << self + instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$|^instance_eval$/ } + + def method_missing(name, *args, &block) + if Model.respond_to?(name) + Model.send(name, *args, &block) + else + ActiveSupport::Deprecation.warn( + "The object passed to the active_record load hook was previously ActiveRecord::Base " \ + "(a Class). Now it is ActiveRecord::Model (a Module). You have called `#{name}' which " \ + "is only defined on ActiveRecord::Base. Please change your code so that it works with " \ + "a module rather than a class. (Model is included in Base, so anything added to Model " \ + "will be available on Base as well.)" + ) + Base.send(name, *args, &block) + end + end + + alias send method_missing + end + end + end + + # Load Base at this point, because the active_record load hook is run in that file. + Base +end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 36417d89f7..adf85c6436 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -1,20 +1,20 @@ require 'active_support/concern' +require 'active_support/core_ext/class/attribute_accessors' module ActiveRecord module ModelSchema extend ActiveSupport::Concern - 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. - cattr_accessor :primary_key_prefix_type, :instance_writer => false - self.primary_key_prefix_type = nil + ## + # :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 name of the prefix string to prepend to every table name. So if set @@ -105,10 +105,6 @@ module ActiveRecord @table_name end - def original_table_name #:nodoc: - deprecated_original_property_getter :table_name - end - # Sets the table name explicitly. Example: # # class Project < ActiveRecord::Base @@ -125,13 +121,6 @@ module ActiveRecord @relation = Relation.new(self, arel_table) end - def set_table_name(value = nil, &block) #:nodoc: - deprecated_property_setter :table_name, value, block - @quoted_table_name = nil - @arel_table = nil - @relation = Relation.new(self, arel_table) - end - # Returns a quoted version of the table name, used to construct SQL statements. def quoted_table_name @quoted_table_name ||= connection.quote_table_name(table_name) @@ -139,10 +128,10 @@ module ActiveRecord # Computes the table name, (re)sets it internally, and returns it. def reset_table_name #:nodoc: - if superclass.abstract_class? - self.table_name = superclass.table_name || compute_table_name + if active_record_super.abstract_class? + self.table_name = active_record_super.table_name || compute_table_name elsif abstract_class? - self.table_name = superclass == Base ? nil : superclass.table_name + self.table_name = active_record_super == Base ? nil : active_record_super.table_name else self.table_name = compute_table_name end @@ -157,24 +146,16 @@ module ActiveRecord if self == Base 'type' else - (@inheritance_column ||= nil) || superclass.inheritance_column + (@inheritance_column ||= nil) || active_record_super.inheritance_column end end - def original_inheritance_column #:nodoc: - deprecated_original_property_getter :inheritance_column - end - # Sets the value of inheritance_column def inheritance_column=(value) @original_inheritance_column = inheritance_column @inheritance_column = value.to_s end - def set_inheritance_column(value = nil, &block) #:nodoc: - deprecated_property_setter :inheritance_column, value, block - end - def sequence_name if base_class == self @sequence_name ||= reset_sequence_name @@ -183,10 +164,6 @@ module ActiveRecord end end - def original_sequence_name #:nodoc: - deprecated_original_property_getter :sequence_name - end - def reset_sequence_name #:nodoc: self.sequence_name = connection.default_sequence_name(table_name, primary_key) end @@ -210,10 +187,6 @@ module ActiveRecord @sequence_name = value.to_s end - def set_sequence_name(value = nil, &block) #:nodoc: - deprecated_property_setter :sequence_name, value, block - end - # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.table_exists?(table_name) @@ -318,7 +291,7 @@ module ActiveRecord base = base_class if self == base # Nested classes are prefixed with singular parent table name. - if parent < ActiveRecord::Base && !parent.abstract_class? + if parent < ActiveRecord::Model && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained += '_' @@ -329,34 +302,6 @@ module ActiveRecord base.table_name end end - - def deprecated_property_setter(property, value, block) - if block - ActiveSupport::Deprecation.warn( - "Calling set_#{property} is deprecated. If you need to lazily evaluate " \ - "the #{property}, define your own `self.#{property}` class method. You can use `super` " \ - "to get the default #{property} where you would have called `original_#{property}`." - ) - - define_attr_method property, value, false, &block - else - ActiveSupport::Deprecation.warn( - "Calling set_#{property} is deprecated. Please use `self.#{property} = 'the_name'` instead." - ) - - define_attr_method property, value, false - end - end - - def deprecated_original_property_getter(property) - ActiveSupport::Deprecation.warn("original_#{property} is deprecated. Define self.#{property} and call super instead.") - - if !instance_variable_defined?("@original_#{property}") && respond_to?("reset_#{property}") - send("reset_#{property}") - else - instance_variable_get("@original_#{property}") - end - end end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a2fe21043f..09ee2ba61d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -9,7 +9,7 @@ module ActiveRecord # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # - # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options @@ -59,8 +59,8 @@ module ActiveRecord @destroyed end - # Returns if the record is persisted, i.e. it's not a new record and it was - # not destroyed. + # Returns true if the record is persisted, i.e. it's not a new record and it was + # not destroyed, otherwise returns false. def persisted? !(new_record? || destroyed?) end @@ -209,7 +209,7 @@ module ActiveRecord # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.assign_attributes(attributes, options) + assign_attributes(attributes, options) save end end @@ -220,7 +220,7 @@ module ActiveRecord # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.assign_attributes(attributes, options) + assign_attributes(attributes, options) save! end end @@ -285,7 +285,7 @@ module ActiveRecord clear_association_cache IdentityMap.without do - fresh_object = self.class.unscoped { self.class.find(self.id, options) } + fresh_object = self.class.unscoped { self.class.find(id, options) } @attributes.update(fresh_object.instance_variable_get('@attributes')) end @@ -368,13 +368,5 @@ module ActiveRecord @new_record = false id end - - # Initializes the attributes array with keys matching the columns from the linked table and - # the values matching the corresponding default value of that column, so - # that a new instance, or one populated from a passed-in Hash, still has all the attributes - # that instances loaded from the database would. - def attributes_from_column_definition - self.class.column_defaults.dup - end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 199eee4359..de299dbe91 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -38,6 +38,7 @@ db_namespace = namespace :db do desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' task :create => :load_config do configs_for_environment.each { |config| create_database(config) } + ActiveRecord::Base.establish_connection(configs_for_environment.first) end def mysql_creation_options(config) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0f57e9831d..bf9b4bf1c9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -168,7 +168,7 @@ module ActiveRecord # This method is designed to perform select by a single column as direct SQL query # Returns <tt>Array</tt> with values of the specified column name - # The values has same data type as column. + # The values has same data type as column. # # Examples: # @@ -177,9 +177,8 @@ module ActiveRecord # Person.where(:confirmed => true).limit(5).pluck(:id) # def pluck(column_name) - scope = self.select(column_name) - self.connection.select_values(scope.to_sql).map! do |value| - type_cast_using_column(value, column_for(column_name)) + klass.connection.select_all(select(column_name).arel).map! do |attributes| + klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes)) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3c8e0f2052..311bf4dc0f 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -187,7 +187,7 @@ module ActiveRecord def exists?(id = false) return false if id.nil? - id = id.id if ActiveRecord::Base === id + id = id.id if ActiveRecord::Model === id join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index a789f48725..eee198e760 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -22,7 +22,7 @@ module ActiveRecord value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty? attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy - values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x} + values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x} ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)} array_predicates = ranges.map {|range| attribute.in(range)} @@ -41,7 +41,7 @@ module ActiveRecord array_predicates.inject {|composite, predicate| composite.or(predicate)} when Range, Arel::Relation attribute.in(value) - when ActiveRecord::Base + when ActiveRecord::Model attribute.eq(value.id) when Class # FIXME: I think we need to deprecate this behavior diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index ffe9b08dce..5398a14fc6 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -13,13 +13,6 @@ module ActiveRecord ActiveRecord::IdentityMap.clear end - # Backport skip to Ruby 1.8. test/unit doesn't support it, so just - # make it a noop. - unless instance_methods.map(&:to_s).include?("skip") - def skip(message) - end - end - def assert_date_from_db(expected, actual, message = nil) # SybaseAdapter doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted @@ -56,18 +49,5 @@ module ActiveRecord ensure ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql end - - def with_kcode(kcode) - if RUBY_VERSION < '1.9' - orig_kcode, $KCODE = $KCODE, kcode - begin - yield - ensure - $KCODE = orig_kcode - end - else - yield - end - end end end |
