diff options
Diffstat (limited to 'activerecord')
62 files changed, 391 insertions, 402 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e7e414571d..e0a7552921 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,25 @@ +* Pluck now works when selecting columns from different tables with the same + name. + + Fixes #15649 + + *Sean Griffin* + +* Remove `cache_attributes` and friends. All attributes are cached. + + *Sean Griffin* + +* Remove deprecated method `ActiveRecord::Base.quoted_locking_column`. + + *Akshay Vishnoi* + +* `ActiveRecord::FinderMethods.find` with block can handle proc parameter as + `Enumerable#find` does. + + Fixes #15382. + + *James Yang* + * Make timezone aware attributes work with PostgreSQL array columns. Fixes #13402. @@ -532,10 +554,6 @@ *Luke Steensen* -* Make possible to change `record_timestamps` inside Callbacks. - - *Tieg Zaharia* - * Fixed error where .persisted? throws SystemStackError for an unsaved model with a custom primary key that didn't save due to validation error. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 07dfc448e7..6222bfe903 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1577,7 +1577,7 @@ module ActiveRecord scope = nil end - habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self) + habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(:has_and_belongs_to_many, name, scope, options, self) builder = Builder::HasAndBelongsToMany.new name, self, options diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 4a04303fb8..f1c36cd047 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -179,7 +179,7 @@ module ActiveRecord def creation_attributes attributes = {} - if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through] + if (reflection.has_one? || reflection.collection?) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 31108cc1aa..519d4d8651 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -105,18 +105,9 @@ module ActiveRecord chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first - if reflection.source_macro == :belongs_to - if reflection.polymorphic? - key = reflection.association_primary_key(assoc_klass) - else - key = reflection.association_primary_key - end - - foreign_key = reflection.foreign_key - else - key = reflection.foreign_key - foreign_key = reflection.active_record_primary_key - end + join_keys = reflection.join_keys(assoc_klass) + key = join_keys.key + foreign_key = join_keys.foreign_key if reflection == chain.last bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 1edd4fa3aa..81fdd681de 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -92,7 +92,7 @@ module ActiveRecord # has_one associations. def invertible_for?(record) inverse = inverse_reflection_for(record) - inverse && inverse.macro == :has_one + inverse && inverse.has_one? end def target_id diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index f359efd496..a1f4f51664 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - valid = super + [:order, :as] + valid = super + [:as] valid += [:through, :source, :source_type] if options[:through] valid end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index c5f7bcae7d..306588ac66 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -57,7 +57,7 @@ module ActiveRecord def ids_writer(ids) pk_column = reflection.primary_key_column ids = Array(ids).reject { |id| id.blank? } - ids.map! { |i| pk_column.type_cast(i) } + ids.map! { |i| pk_column.type_cast_from_user(i) } replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids)) end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 954128064d..da9b125fd6 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -89,7 +89,7 @@ module ActiveRecord end def through_scope_attributes - scope.where_values_hash(through_association.reflection.name.to_s) + scope.where_values_hash(through_association.reflection.name.to_s).except!(through_association.reflection.foreign_key) end def save_through_record(record) @@ -105,9 +105,9 @@ module ActiveRecord inverse = source_reflection.inverse_of if inverse - if inverse.macro == :has_many + if inverse.collection? record.send(inverse.name) << build_through_record(record) - elsif inverse.macro == :has_one + elsif inverse.has_one? record.send("#{inverse.name}=", build_through_record(record)) end end @@ -170,7 +170,7 @@ module ActiveRecord klass.decrement_counter counter, records.map(&:id) end - if through_reflection.macro == :has_many && update_through_counter?(method) + if through_reflection.collection? && update_through_counter?(method) update_counter(-count, through_reflection) end @@ -187,7 +187,7 @@ module ActiveRecord records.each do |record| through_records = through_records_for(record) - if through_reflection.macro == :has_many + if through_reflection.collection? through_records.each { |r| through_association.target.delete(r) } else if through_records.include?(through_association.target) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 35659766d3..fbb4551b22 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -144,7 +144,7 @@ module ActiveRecord column_aliases = aliases.column_aliases join_root result_set.each { |row_hash| - primary_id = type_caster.type_cast row_hash[primary_key] + primary_id = type_caster.type_cast_from_database row_hash[primary_key] parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases) construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) } diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index ae3785638a..7b7e37884b 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -35,35 +35,22 @@ module ActiveRecord extend ActiveSupport::Concern - ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :time, :date] - - included do - class_attribute :attribute_types_cached_by_default, instance_writer: false - 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 be cached. Usually caching only pays off for attributes - # with expensive conversion methods, like time related columns (e.g. - # +created_at+, +updated_at+). - def cache_attributes(*attribute_names) - cached_attributes.merge attribute_names.map { |attr| attr.to_s } + [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name| + define_method method_name do |*| + cached_attributes_deprecation_warning(method_name) + true + end end - # Returns the attributes which are cached. By default time related columns - # with datatype <tt>:datetime, :time, :date</tt> are cached. - def cached_attributes - @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set - end + protected - # Returns +true+ if the provided attribute is being cached. - def cache_attribute?(attr_name) - cached_attributes.include?(attr_name) + def cached_attributes_deprecation_warning(method_name) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + Calling `#{method_name}` is no longer necessary. All attributes are cached. + MESSAGE end - protected - if Module.methods_transplantable? def define_method_attribute(name) method = ReaderMethodCache[name] @@ -89,16 +76,6 @@ module ActiveRecord end end end - - private - - def cacheable_column?(column) - if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - true - else - attribute_types_cached_by_default.include?(column.type) - end - end end # Returns the value of the attribute identified by <tt>attr_name</tt> after @@ -122,11 +99,7 @@ module ActiveRecord return block_given? ? yield(name) : nil } - if self.class.cache_attribute?(name) - @attributes[name] = column.type_cast(value) - else - column.type_cast value - end + @attributes[name] = column.type_cast_from_database(value) } 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 c1c3987cf5..abad949ef4 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,21 +1,22 @@ module ActiveRecord module AttributeMethods module TimeZoneConversion - class Type # :nodoc: - delegate :type, :type_cast_for_database, to: :@column - - def initialize(column) - @column = column + class Type < SimpleDelegator # :nodoc: + def type_cast_from_database(value) + convert_time_to_time_zone(super) end - def type_cast(value) - value = @column.type_cast(value) - convert_value_to_time_zone(value) + def type_cast_from_user(value) + if value.is_a?(Array) + value.map { |v| type_cast_from_user(v) } + elsif value.respond_to?(:in_time_zone) + value.in_time_zone + end end - def convert_value_to_time_zone(value) + def convert_time_to_time_zone(value) if value.is_a?(Array) - value.map { |v| convert_value_to_time_zone(v) } + value.map { |v| convert_time_to_time_zone(v) } elsif value.acts_like?(:time) value.in_time_zone else @@ -35,26 +36,6 @@ module ActiveRecord end module ClassMethods - protected - # Defined for all +datetime+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. - def define_method_attribute=(attr_name) - if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) - method_body, line = <<-EOV, __LINE__ + 1 - def #{attr_name}=(time) - time_with_zone = convert_value_to_time_zone(time) - previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name}) - write_attribute(:#{attr_name}, time) - #{attr_name}_will_change! if previous_time != time_with_zone - @attributes["#{attr_name}"] = time_with_zone - end - EOV - generated_attribute_methods.module_eval(method_body, __FILE__, line) - else - super - end - end - private def create_time_zone_conversion_attribute?(name, column) time_zone_aware_attributes && @@ -62,16 +43,6 @@ module ActiveRecord (:datetime == column.type) end end - - private - - def convert_value_to_time_zone(value) - if value.is_a?(Array) - value.map { |v| convert_value_to_time_zone(v) } - elsif value.respond_to?(:in_time_zone) - value.in_time_zone - end - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 5203b30462..b72a6219b0 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -70,7 +70,7 @@ module ActiveRecord attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key @attributes.delete(attr_name) - column = column_for_attribute(attr_name) + column = type_for_attribute(attr_name) unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name) raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" @@ -80,13 +80,11 @@ module ActiveRecord # so we don't attempt to typecast multiple times. if column.binary? @attributes[attr_name] = value + elsif should_type_cast + @attributes[attr_name] = column.type_cast_from_user(value) end - if should_type_cast - @raw_attributes[attr_name] = column.type_cast_for_write(value) - else - @raw_attributes[attr_name] = value - end + @raw_attributes[attr_name] = value end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 74e2a8e6b9..b3c3e26c9f 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -187,7 +187,7 @@ module ActiveRecord # Doesn't use after_save as that would save associations added in after_create/after_update twice after_create save_method after_update save_method - elsif reflection.macro == :has_one + elsif reflection.has_one? define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) # Configures two callbacks instead of a single after_save so that # the model may rely on their execution order relative to its diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e8c067f758..e4d0abb8ef 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -311,8 +311,8 @@ module ActiveRecord #:nodoc: include Locking::Optimistic include Locking::Pessimistic include AttributeMethods - include Timestamp include Callbacks + include Timestamp include Associations include ActiveModel::SecurePassword include AutosaveAssociation diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index d3e172927d..5a0efe49c7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -1,5 +1,3 @@ -require 'ipaddr' - module ActiveRecord module ConnectionAdapters # :nodoc: # The goal of this module is to move Adapter specific column diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 23434df1fe..72c6990ba5 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -17,7 +17,7 @@ module ActiveRecord delegate :type, :precision, :scale, :limit, :klass, :accessor, :text?, :number?, :binary?, :serialized?, :changed?, - :type_cast, :type_cast_for_write, :type_cast_for_database, + :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, :type_cast_for_schema, to: :cast_type @@ -52,7 +52,7 @@ module ActiveRecord end def default - @default ||= type_cast(@original_default) + @default ||= type_cast_from_database(@original_default) end def with_type(type) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 971f5eed7e..bb54de05c8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -67,10 +67,6 @@ module ActiveRecord end end - def string_to_array(string, oid) # :nodoc: - parse_pg_array(string).map {|val| type_cast_array(oid, val)} - end - private HstorePair = begin @@ -103,14 +99,6 @@ module ActiveRecord "\"#{value}\"" end end - - def type_cast_array(oid, value) - if ::Array === value - value.map {|item| type_cast_array(oid, item)} - else - oid.type_cast value - end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index a579746815..847fd4dded 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -4,6 +4,8 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: + extend PostgreSQL::Cast + attr_accessor :array def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) @@ -17,24 +19,6 @@ module ActiveRecord @default_function = default_function end - - # :stopdoc: - class << self - include PostgreSQL::Cast - - # Loads pg_array_parser if available. String parsing can be - # performed quicker by a native extension, which will not create - # a large amount of Ruby objects that will need to be garbage - # collected. pg_array_parser has a C and Java extension - begin - require 'pg_array_parser' - include PgArrayParser - rescue LoadError - require 'active_record/connection_adapters/postgresql/array_parser' - include PostgreSQL::ArrayParser - end - end - # :startdoc: end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index 0e9dcd8c0c..87817fe8d1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -10,11 +10,33 @@ module ActiveRecord @subtype = subtype end - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype + def type_cast_from_database(value) + if value.is_a?(::String) + type_cast_array(parse_pg_array(value)) else - value + super + end + end + + # Loads pg_array_parser if available. String parsing can be + # performed quicker by a native extension, which will not create + # a large amount of Ruby objects that will need to be garbage + # collected. pg_array_parser has a C and Java extension + begin + require 'pg_array_parser' + include PgArrayParser + rescue LoadError + require 'active_record/connection_adapters/postgresql/array_parser' + include PostgreSQL::ArrayParser + end + + private + + def type_cast_array(value) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item) } + else + @subtype.type_cast_from_database(value) end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index a65ca83f77..88de816d4f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -7,12 +7,16 @@ module ActiveRecord :hstore end - def type_cast_for_write(value) - ConnectionAdapters::PostgreSQLColumn.hstore_to_string(value) + def type_cast_from_user(value) + type_cast_from_database(type_cast_for_database(value)) + end + + def type_cast_from_database(value) + ConnectionAdapters::PostgreSQLColumn.string_to_hstore(value) end - def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_hstore value + def type_cast_for_database(value) + ConnectionAdapters::PostgreSQLColumn.hstore_to_string(value) end def accessor diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index c87422fe32..b4fed1bcab 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -7,12 +7,16 @@ module ActiveRecord :json end - def type_cast_for_write(value) - ConnectionAdapters::PostgreSQLColumn.json_to_string(value) + def type_cast_from_user(value) + type_cast_from_database(type_cast_for_database(value)) + end + + def type_cast_from_database(value) + ConnectionAdapters::PostgreSQLColumn.string_to_json(value) end - def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_json value + def type_cast_for_database(value) + ConnectionAdapters::PostgreSQLColumn.json_to_string(value) end def accessor diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index a0d8a94c74..c289ba8980 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -29,7 +29,7 @@ module ActiveRecord end def type_cast_single(value) - infinity?(value) ? value : @subtype.type_cast(value) + infinity?(value) ? value : @subtype.type_cast_from_database(value) end def cast_value(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 283ca81f94..71b05cdbae 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -348,14 +348,14 @@ module ActiveRecord if supports_extensions? res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", 'SCHEMA' - res.column_types['enabled'].type_cast res.rows.first.first + res.column_types['enabled'].type_cast_from_database res.rows.first.first end end def extensions if supports_extensions? res = exec_query "SELECT extname from pg_extension", "SCHEMA" - res.rows.map { |r| res.column_types['extname'].type_cast r.first } + res.rows.map { |r| res.column_types['extname'].type_cast_from_database r.first } else super end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 79388f53b5..7edaf256c7 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -356,6 +356,7 @@ module ActiveRecord def encode_with(coder) coder['raw_attributes'] = @raw_attributes coder['attributes'] = @attributes + coder['column_types'] = @column_types_override coder['new_record'] = new_record? end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6ba667b996..95fcbbe99a 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -462,13 +462,7 @@ module ActiveRecord @config = config # Remove string values that aren't constants or subclasses of AR - @class_names.delete_if { |k,klass| - unless klass.is_a? Class - klass = klass.safe_constantize - ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `set_fixture_class` will be removed in Rails 4.2. Use the class itself instead.") - end - !insert_class(@class_names, k, klass) - } + @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) } end def [](fs_name) @@ -574,10 +568,6 @@ module ActiveRecord @config = config @model_class = nil - if class_name.is_a?(String) - ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `FixtureSet.new` will be removed in Rails 4.2. Use the class itself instead.") - end - if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? @model_class = class_name else diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index f7ceff7469..4528d8783c 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -151,12 +151,6 @@ module ActiveRecord @locking_column end - # Quote the column name used for optimistic locking. - def quoted_locking_column - ActiveSupport::Deprecation.warn "ActiveRecord::Base.quoted_locking_column is deprecated and will be removed in Rails 4.2 or later." - connection.quote_column_name(locking_column) - end - # Reset the column used for optimistic locking back to the +lock_version+ default. def reset_locking_column self.locking_column = DEFAULT_LOCKING_COLUMN diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index baf2b5fbf8..9e1afd32e6 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -51,6 +51,8 @@ module ActiveRecord self.pluralize_table_names = true self.inheritance_column = 'type' + + delegate :type_for_attribute, to: :class end module ClassMethods @@ -218,21 +220,25 @@ module ActiveRecord end def column_types # :nodoc: - @column_types ||= decorate_columns(columns_hash.dup) + @column_types ||= decorate_types(build_types_hash) + end + + def type_for_attribute(attr_name) # :nodoc: + column_types.fetch(attr_name) { Type::Value.new } end - def decorate_columns(columns_hash) # :nodoc: - return if columns_hash.empty? + def decorate_types(types) # :nodoc: + return if types.empty? @time_zone_column_names ||= self.columns_hash.find_all do |name, col| create_time_zone_conversion_attribute?(name, col) end.map!(&:first) @time_zone_column_names.each do |name| - columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name]) + types[name] = AttributeMethods::TimeZoneConversion::Type.new(types[name]) end - columns_hash + types end # Returns a hash where the keys are column names and the values are @@ -245,7 +251,7 @@ module ActiveRecord # are the default values suitable for use in `@raw_attriubtes` def raw_column_defaults # :nodoc: @raw_column_defauts ||= Hash[column_defaults.map { |name, default| - [name, columns_hash[name].type_cast_for_write(default)] + [name, columns_hash[name].type_cast_for_database(default)] }] end @@ -329,6 +335,10 @@ module ActiveRecord base.table_name end end + + def build_types_hash + Hash[columns.map { |column| [column.name, column.cast_type] }] + end end end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 1242f49e28..8a2a06f2ca 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -516,7 +516,7 @@ module ActiveRecord # Determines if a hash contains a truthy _destroy key. def has_destroy_flag?(hash) - Type::Boolean.new.type_cast(hash['_destroy']) + Type::Boolean.new.type_cast_from_user(hash['_destroy']) end # Determines if a new record should be rejected by checking diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 525289c270..3ab8ec4836 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -48,7 +48,6 @@ module ActiveRecord # how this "single-table" inheritance mapping is implemented. def instantiate(attributes, column_types = {}) klass = discriminate_class_for_record(attributes) - column_types = klass.decorate_columns(column_types.dup) klass.allocate.init_with( 'raw_attributes' => attributes, 'column_types' => column_types, diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 1fe54cea3f..39817703cd 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -40,7 +40,7 @@ module ActiveRecord column_types = {} if result_set.respond_to? :column_types - column_types = result_set.column_types.merge(columns_hash) + column_types = result_set.column_types.except(*columns_hash.keys) else ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`" end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 4d5203612c..95a3c70c93 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -188,6 +188,23 @@ module ActiveRecord active_record == other_aggregation.active_record end + JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: + + def join_keys(assoc_klass) + if source_macro == :belongs_to + if polymorphic? + reflection_key = association_primary_key(assoc_klass) + else + reflection_key = association_primary_key + end + reflection_foreign_key = foreign_key + else + reflection_key = foreign_key + reflection_foreign_key = active_record_primary_key + end + JoinKeys.new(reflection_key, reflection_foreign_key) + end + private def derive_class_name name.to_s.camelize @@ -228,7 +245,7 @@ module ActiveRecord def initialize(macro, name, scope, options, active_record) super - @collection = [:has_many, :has_and_belongs_to_many].include?(macro) + @collection = macro == :has_many @automatic_inverse_of = nil @type = options[:as] && "#{options[:as]}_type" @foreign_type = options[:foreign_type] || "#{name}_type" @@ -392,7 +409,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi # * you use autosave; <tt>autosave: true</tt> # * the association is a +has_many+ association def validate? - !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many) + !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?) end # Returns +true+ if +self+ is a +belongs_to+ reflection. @@ -400,6 +417,11 @@ Joining, Preloading and eager loading of these associations is deprecated and wi macro == :belongs_to end + # Returns +true+ if +self+ is a +has_one+ reflection. + def has_one? + macro == :has_one + end + def association_class case macro when :belongs_to @@ -538,6 +560,13 @@ Joining, Preloading and eager loading of these associations is deprecated and wi end end + class HasAndBelongsToManyReflection < AssociationReflection #:nodoc: + def initialize(macro, name, scope, options, active_record) + super + @collection = true + end + end + # Holds all the meta-data about a :through association as it was specified # in the Active Record class. class ThroughReflection < AssociationReflection #:nodoc: @@ -739,7 +768,7 @@ directive on your declaration like: raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection) end - if macro == :has_one && through_reflection.collection? + if has_one? && through_reflection.collection? raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection) end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 11ab1b4595..028e4d80ab 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -176,10 +176,8 @@ module ActiveRecord } end - result = result.map do |attributes| - values = attributes.values - - columns.zip(values).map { |column, value| column.type_cast value } + result = result.rows.map do |values| + columns.zip(values).map { |column, value| column.type_cast_from_database value } end columns.one? ? result.map!(&:first) : result end @@ -379,7 +377,7 @@ module ActiveRecord end def type_cast_using_column(value, column) - column ? column.type_cast(value) : value + column ? column.type_cast_from_database(value) : value end # TODO: refactor to allow non-string `select_values` (eg. Arel nodes). diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 47e90e9021..0c9c761f97 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -65,7 +65,7 @@ module ActiveRecord # # returns an Array of the required fields, available since Rails 3.1. def find(*args) if block_given? - to_a.find { |*block_args| yield(*block_args) } + to_a.find(*args) { |*block_args| yield(*block_args) } else find_with_ids(*args) end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 293189fa69..ae53f66d7a 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -31,7 +31,7 @@ module ActiveRecord class Result include Enumerable - IDENTITY_TYPE = Class.new { def type_cast(v); v; end }.new # :nodoc: + IDENTITY_TYPE = Type::Value.new # :nodoc: attr_reader :columns, :rows, :column_types diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 6c30ccab72..e2e37e7c00 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -99,9 +99,11 @@ module ActiveRecord end def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update) - if (timestamps = timestamp_names.map { |attr| self[attr] }.compact).present? - timestamps.map { |ts| ts.to_time }.max - end + timestamp_names + .map { |attr| self[attr] } + .compact + .map(&:to_time) + .max end def current_time_from_proper_timezone diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb index bc93f6e1bf..3bf29b5026 100644 --- a/activerecord/lib/active_record/type/binary.rb +++ b/activerecord/lib/active_record/type/binary.rb @@ -9,7 +9,16 @@ module ActiveRecord true end + def type_cast(value) + if value.is_a?(Data) + value.to_s + else + super + end + end + def type_cast_for_database(value) + return if value.nil? Data.new(super) end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 0866383de9..e6d84eadc0 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -9,7 +9,7 @@ module ActiveRecord super(subtype) end - def type_cast(value) + def type_cast_from_database(value) if is_default_value?(value) value else @@ -17,15 +17,17 @@ module ActiveRecord end end - def type_cast_for_write(value) + def type_cast_from_user(value) + type_cast_from_database(type_cast_for_database(value)) + end + + def type_cast_for_database(value) return if value.nil? unless is_default_value?(value) super coder.dump(value) end end - alias type_cast_for_database type_cast_for_write - def serialized? true end @@ -34,6 +36,17 @@ module ActiveRecord ActiveRecord::Store::IndifferentHashAccessor end + def init_with(coder) + @subtype = coder['subtype'] + @coder = coder['coder'] + __setobj__(@subtype) + end + + def encode_with(coder) + coder['subtype'] = @subtype + coder['coder'] = @coder + end + private def is_default_value?(value) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 1c41b28646..efcdab1c0e 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -16,15 +16,16 @@ module ActiveRecord # must override this method. def type; end - # Takes an input from the database, or from attribute setters, - # and casts it to a type appropriate for this object. This method - # should not be overriden by subclasses. Instead, override `cast_value`. - def type_cast(value) - cast_value(value) unless value.nil? + def type_cast_from_database(value) + type_cast(value) + end + + def type_cast_from_user(value) + type_cast(value) end def type_cast_for_database(value) - type_cast_for_write(value) + value end def type_cast_for_schema(value) @@ -50,10 +51,6 @@ module ActiveRecord def klass # :nodoc: end - def type_cast_for_write(value) # :nodoc: - value - end - # +old_value+ will always be type-cast. # +new_value+ will come straight from the database # or from assignment, so it could be anything. Types @@ -64,6 +61,12 @@ module ActiveRecord end private + # Takes an input from the database, or from attribute setters, + # and casts it to a type appropriate for this object. This method + # should not be overriden by subclasses. Instead, override `cast_value`. + def type_cast(value) # :api: public + cast_value(value) unless value.nil? + end # Responsible for casting values from external sources to the appropriate # type. Called by `type_cast` for all values except `nil`. diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 1699380eb3..28106d3772 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -105,7 +105,7 @@ module ActiveRecord result = @conn.exec_query('SELECT status FROM ex') - assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status']) end end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 1d431f1666..66750deb68 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -90,9 +90,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end def test_type_cast_array - assert_equal(['1', '2', '3'], @column.type_cast('{1,2,3}')) - assert_equal([], @column.type_cast('{}')) - assert_equal([nil], @column.type_cast('{NULL}')) + assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}')) + assert_equal([], @column.type_cast_from_database('{}')) + assert_equal([nil], @column.type_cast_from_database('{NULL}')) end def test_type_cast_integers diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 3f8a5d1062..7872f91943 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -33,16 +33,16 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = "\u001F\x8B" assert_equal('UTF-8', data.encoding.name) - assert_equal('ASCII-8BIT', @column.type_cast(data).encoding.name) + assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name) end def test_type_cast_binary_value data = "\u001F\x8B".force_encoding("BINARY") - assert_equal(data, @column.type_cast(data)) + assert_equal(data, @column.type_cast_from_database(data)) end def test_type_case_nil - assert_equal(nil, @column.type_cast(nil)) + assert_equal(nil, @column.type_cast_from_database(nil)) end def test_read_value diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index a925263098..0b48fe9af8 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -90,7 +90,11 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase end end - def type_cast_for_write(value) + def type_cast_from_user(value) + value + end + + def type_cast_for_database(value) return if value.nil? "(#{value.city},#{value.street})" end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 5f84c893c0..d26cda46fa 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -121,7 +121,7 @@ module ActiveRecord name = @subscriber.payloads.last[:statement_name] assert name res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})") - plan = res.column_types['QUERY PLAN'].type_cast res.rows.first.first + plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first assert_operator plan.length, :>, 0 end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index a6482786c7..a25c9cb5e4 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -106,6 +106,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_cast_value_on_write x = Hstore.new tags: {"bool" => true, "number" => 5} + assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast) assert_equal({"bool" => "true", "number" => "5"}, x.tags) x.save assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) @@ -117,11 +118,11 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase data = "\"1\"=>\"2\"" hash = @column.class.string_to_hstore data assert_equal({'1' => '2'}, hash) - assert_equal({'1' => '2'}, @column.type_cast(data)) + assert_equal({'1' => '2'}, @column.type_cast_from_database(data)) - assert_equal({}, @column.type_cast("")) - assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) + assert_equal({}, @column.type_cast_from_database("")) + assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) end def test_with_store_accessors @@ -179,31 +180,31 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase end def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) end def test_parse2 - assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) + assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ ")) end def test_parse3 - assert_equal({"=" => ">"}, @column.type_cast("==>>")) + assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>")) end def test_parse4 - assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) + assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w')) end def test_parse5 - assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) + assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w')) end def test_parse6 - assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) + assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w')) end def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) + assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w')) end def test_rewrite @@ -295,24 +296,18 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal({ }, hstore.reload.tags) end - # FIXME: remove this lambda once `serialize` no longer issues a db connection. - LAZY_MODELS = lambda do - return if defined?(TagCollection) - - class TagCollection - def initialize(hash); @hash = hash end - def to_hash; @hash end - def self.load(hash); new(hash) end - def self.dump(object); object.to_hash end - end + class TagCollection + def initialize(hash); @hash = hash end + def to_hash; @hash end + def self.load(hash); new(hash) end + def self.dump(object); object.to_hash end + end - class HstoreWithSerialize < Hstore - serialize :tags, TagCollection - end + class HstoreWithSerialize < Hstore + serialize :tags, TagCollection end def test_hstore_with_serialized_attributes - LAZY_MODELS.call HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) record = HstoreWithSerialize.first assert_instance_of TagCollection, record.tags @@ -323,7 +318,6 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase end def test_clone_hstore_with_serialized_attributes - LAZY_MODELS.call HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) record = HstoreWithSerialize.first dupe = record.dup diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 61d4e2b8ae..3ee8839823 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -68,6 +68,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase def test_cast_value_on_write x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} + assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload) x.save assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload) @@ -79,11 +80,11 @@ class PostgresqlJSONTest < ActiveRecord::TestCase data = "{\"a_key\":\"a_value\"}" hash = column.class.string_to_json data assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, column.type_cast(data)) + assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data)) - assert_equal({}, column.type_cast("{}")) - assert_equal({'key'=>nil}, column.type_cast('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"}))) + assert_equal({}, column.type_cast_from_database("{}")) + assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 3e33477bff..bdfeedafab 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -49,10 +49,10 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase def test_money_type_cast column = PostgresqlMoney.columns_hash['wealth'] - assert_equal(12345678.12, column.type_cast("$12,345,678.12")) - assert_equal(12345678.12, column.type_cast("$12.345.678,12")) - assert_equal(-1.15, column.type_cast("-$1.15")) - assert_equal(-2.25, column.type_cast("($2.25)")) + assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12")) + assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12")) + assert_equal(-1.15, column.type_cast_from_user("-$1.15")) + assert_equal(-2.25, column.type_cast_from_user("($2.25)")) end def test_schema_dumping diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index 4d9cfe55f5..0f6e39322c 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -156,7 +156,7 @@ _SQL assert_equal 0.5..0.7, @first_range.float_range assert_equal 0.5...0.7, @second_range.float_range assert_equal 0.5...Float::INFINITY, @third_range.float_range - assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.float_range) assert_nil @empty_range.float_range end diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb index f17cc02f53..35393753a2 100644 --- a/activerecord/test/cases/attribute_decorators_test.rb +++ b/activerecord/test/cases/attribute_decorators_test.rb @@ -12,9 +12,11 @@ module ActiveRecord super(delegate) end - def type_cast(value) + def type_cast_from_user(value) "#{super} #{@decoration}" end + + alias type_cast_from_database type_cast_from_user end setup do diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 139fe9c04b..a366caf875 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -517,43 +517,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_only_time_related_columns_are_meant_to_be_cached_by_default - expected = %w(datetime time date).sort - assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort - end - - def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default - default_attributes = Topic.cached_attributes - Topic.cache_attributes :replies_count - expected = default_attributes + ["replies_count"] - assert_equal expected.sort, Topic.cached_attributes.sort - Topic.instance_variable_set "@cached_attributes", nil - end - - def test_cacheable_columns_are_actually_cached - assert_equal cached_columns.sort, Topic.cached_attributes.sort - end - - def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else - t = topics(:first) - cache = t.instance_variable_get "@attributes" - - assert_not_nil cache - assert cache.empty? + def test_deprecated_cache_attributes + assert_deprecated do + Topic.cache_attributes :replies_count + end - all_columns = Topic.columns.map(&:name) - uncached_columns = all_columns - cached_columns + assert_deprecated do + Topic.cached_attributes + end - all_columns.each do |attr_name| - attribute_gets_cached = Topic.cache_attribute?(attr_name) - val = t.send attr_name unless attr_name == "type" - if attribute_gets_cached - assert cached_columns.include?(attr_name) - assert_equal val, cache[attr_name] - else - assert uncached_columns.include?(attr_name) - assert !cache.include?(attr_name) - end + assert_deprecated do + Topic.cache_attribute? :replies_count end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index fad51f4924..dc8f25b0e7 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1489,7 +1489,7 @@ class BasicsTest < ActiveRecord::TestCase attrs.delete 'id' typecast = Class.new { - def type_cast value + def type_cast_from_database value "t.lo" end } diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index dfc6a7ec67..222b1505a8 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -610,4 +610,11 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [1,2,3,4,5], taks_relation.pluck(:id) assert_equal [false, true, true, true, true], taks_relation.pluck(:approved) end + + def test_pluck_columns_with_same_name + expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]] + actual = Topic.joins(:replies) + .pluck('topics.title', 'replies_topics.title') + assert_equal expected, actual + end end diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb index 3958c3bfff..d5c1dc1e5d 100644 --- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -85,7 +85,7 @@ module ActiveRecord cast_type = @connection.type_map.lookup(type) assert_equal :decimal, cast_type.type - assert_equal 2, cast_type.type_cast(2.1) + assert_equal 2, cast_type.type_cast_from_user(2.1) end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 2beac84fb1..87f24e32b2 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -616,17 +616,15 @@ class DirtyTest < ActiveRecord::TestCase end end - test "defaults with type that implements `type_cast_for_write`" do + test "defaults with type that implements `type_cast_for_database`" do type = Class.new(ActiveRecord::Type::Value) do def type_cast(value) value.to_i end - def type_cast_for_write(value) + def type_cast_for_database(value) value.to_s end - - alias type_cast_for_database type_cast_for_write end model_class = Class.new(ActiveRecord::Base) do diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index c0440744e9..bd77c412f6 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -33,6 +33,17 @@ class FinderTest < ActiveRecord::TestCase assert_equal(topics(:first).title, Topic.find(1).title) end + def test_find_with_proc_parameter_and_block + exception = assert_raises(RuntimeError) do + Topic.all.find(-> { raise "should happen" }) { |e| e.title == "non-existing-title" } + end + assert_equal "should happen", exception.message + + assert_nothing_raised(RuntimeError) do + Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title } + end + end + def test_find_passing_active_record_object_is_deprecated assert_deprecated do Topic.find(Topic.last) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 8bbc0af758..042fdaf0bb 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -84,12 +84,6 @@ class FixturesTest < ActiveRecord::TestCase assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" end - def test_create_symbol_fixtures_is_deprecated - assert_deprecated do - ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => 'Course') { Course.connection } - end - end - def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 93fd3b9605..c221430757 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -272,10 +272,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert p.treasures.empty? assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? end - - def test_quoted_locking_column_is_deprecated - assert_deprecated { ActiveRecord::Base.quoted_locking_column } - end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 793e193329..c6e73f78cc 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -95,8 +95,8 @@ class ReflectionTest < ActiveRecord::TestCase column = @first.column_for_attribute("attribute_that_doesnt_exist") object = Object.new - assert_equal object, column.type_cast(object) - assert_equal object, column.type_cast_for_write(object) + assert_equal object, column.type_cast_from_database(object) + assert_equal object, column.type_cast_from_user(object) assert_equal object, column.type_cast_for_database(object) end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index e2a18d138d..5f02d39e32 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -394,7 +394,7 @@ class SchemaDumperTest < ActiveRecord::TestCase output = standard_dump assert_no_match %r{create_table "foo_.+_bar"}, output - assert_no_match %r{create_index "foo_.+_bar"}, output + assert_no_match %r{add_index "foo_.+_bar"}, output assert_no_match %r{create_table "schema_migrations"}, output ensure migration.migrate(:down) diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 77ab427be0..0472246f71 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -71,24 +71,6 @@ class TimestampTest < ActiveRecord::TestCase assert_equal @previously_updated_at, @developer.updated_at end - def test_saving_when_callback_sets_record_timestamps_to_false_doesnt_update_its_timestamp - klass = Class.new(Developer) do - before_update :cancel_record_timestamps - def cancel_record_timestamps - self.record_timestamps = false - return true - end - end - - developer = klass.first - previously_updated_at = developer.updated_at - - developer.name = "New Name" - developer.save! - - assert_equal previously_updated_at, developer.updated_at - end - def test_touching_an_attribute_updates_timestamp previously_created_at = @developer.created_at @developer.touch(:created_at) @@ -399,6 +381,19 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal time, pet.updated_at end + def test_timestamp_column_values_are_present_in_the_callbacks + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'people' + + before_create do + self.born_at = self.created_at + end + end + + person = klass.create first_name: 'David' + assert_not_equal person.born_at, nil + end + def test_timestamp_attributes_for_create toy = Toy.first assert_equal [:created_at, :created_on], toy.send(:timestamp_attributes_for_create) diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index 5d5f442d3a..731f8cfba3 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -6,141 +6,141 @@ module ActiveRecord class TypesTest < ActiveRecord::TestCase def test_type_cast_boolean type = Type::Boolean.new - assert type.type_cast('').nil? - assert type.type_cast(nil).nil? - - assert type.type_cast(true) - assert type.type_cast(1) - assert type.type_cast('1') - assert type.type_cast('t') - assert type.type_cast('T') - assert type.type_cast('true') - assert type.type_cast('TRUE') - assert type.type_cast('on') - assert type.type_cast('ON') + assert type.type_cast_from_user('').nil? + assert type.type_cast_from_user(nil).nil? + + assert type.type_cast_from_user(true) + assert type.type_cast_from_user(1) + assert type.type_cast_from_user('1') + assert type.type_cast_from_user('t') + assert type.type_cast_from_user('T') + assert type.type_cast_from_user('true') + assert type.type_cast_from_user('TRUE') + assert type.type_cast_from_user('on') + assert type.type_cast_from_user('ON') # explicitly check for false vs nil - assert_equal false, type.type_cast(false) - assert_equal false, type.type_cast(0) - assert_equal false, type.type_cast('0') - assert_equal false, type.type_cast('f') - assert_equal false, type.type_cast('F') - assert_equal false, type.type_cast('false') - assert_equal false, type.type_cast('FALSE') - assert_equal false, type.type_cast('off') - assert_equal false, type.type_cast('OFF') - assert_equal false, type.type_cast(' ') - assert_equal false, type.type_cast("\u3000\r\n") - assert_equal false, type.type_cast("\u0000") - assert_equal false, type.type_cast('SOMETHING RANDOM') + assert_equal false, type.type_cast_from_user(false) + assert_equal false, type.type_cast_from_user(0) + assert_equal false, type.type_cast_from_user('0') + assert_equal false, type.type_cast_from_user('f') + assert_equal false, type.type_cast_from_user('F') + assert_equal false, type.type_cast_from_user('false') + assert_equal false, type.type_cast_from_user('FALSE') + assert_equal false, type.type_cast_from_user('off') + assert_equal false, type.type_cast_from_user('OFF') + assert_equal false, type.type_cast_from_user(' ') + assert_equal false, type.type_cast_from_user("\u3000\r\n") + assert_equal false, type.type_cast_from_user("\u0000") + assert_equal false, type.type_cast_from_user('SOMETHING RANDOM') end def test_type_cast_string type = Type::String.new - assert_equal "1", type.type_cast(true) - assert_equal "0", type.type_cast(false) - assert_equal "123", type.type_cast(123) + assert_equal "1", type.type_cast_from_user(true) + assert_equal "0", type.type_cast_from_user(false) + assert_equal "123", type.type_cast_from_user(123) end def test_type_cast_integer type = Type::Integer.new - assert_equal 1, type.type_cast(1) - assert_equal 1, type.type_cast('1') - assert_equal 1, type.type_cast('1ignore') - assert_equal 0, type.type_cast('bad1') - assert_equal 0, type.type_cast('bad') - assert_equal 1, type.type_cast(1.7) - assert_equal 0, type.type_cast(false) - assert_equal 1, type.type_cast(true) - assert_nil type.type_cast(nil) + assert_equal 1, type.type_cast_from_user(1) + assert_equal 1, type.type_cast_from_user('1') + assert_equal 1, type.type_cast_from_user('1ignore') + assert_equal 0, type.type_cast_from_user('bad1') + assert_equal 0, type.type_cast_from_user('bad') + assert_equal 1, type.type_cast_from_user(1.7) + assert_equal 0, type.type_cast_from_user(false) + assert_equal 1, type.type_cast_from_user(true) + assert_nil type.type_cast_from_user(nil) end def test_type_cast_non_integer_to_integer type = Type::Integer.new - assert_nil type.type_cast([1,2]) - assert_nil type.type_cast({1 => 2}) - assert_nil type.type_cast((1..2)) + assert_nil type.type_cast_from_user([1,2]) + assert_nil type.type_cast_from_user({1 => 2}) + assert_nil type.type_cast_from_user((1..2)) end def test_type_cast_activerecord_to_integer type = Type::Integer.new firm = Firm.create(:name => 'Apple') - assert_nil type.type_cast(firm) + assert_nil type.type_cast_from_user(firm) end def test_type_cast_object_without_to_i_to_integer type = Type::Integer.new - assert_nil type.type_cast(Object.new) + assert_nil type.type_cast_from_user(Object.new) end def test_type_cast_nan_and_infinity_to_integer type = Type::Integer.new - assert_nil type.type_cast(Float::NAN) - assert_nil type.type_cast(1.0/0.0) + assert_nil type.type_cast_from_user(Float::NAN) + assert_nil type.type_cast_from_user(1.0/0.0) end def test_type_cast_float type = Type::Float.new - assert_equal 1.0, type.type_cast("1") + assert_equal 1.0, type.type_cast_from_user("1") end def test_type_cast_decimal type = Type::Decimal.new - assert_equal BigDecimal.new("0"), type.type_cast(BigDecimal.new("0")) - assert_equal BigDecimal.new("123"), type.type_cast(123.0) - assert_equal BigDecimal.new("1"), type.type_cast(:"1") + assert_equal BigDecimal.new("0"), type.type_cast_from_user(BigDecimal.new("0")) + assert_equal BigDecimal.new("123"), type.type_cast_from_user(123.0) + assert_equal BigDecimal.new("1"), type.type_cast_from_user(:"1") end def test_type_cast_binary type = Type::Binary.new - assert_equal nil, type.type_cast(nil) - assert_equal "1", type.type_cast("1") - assert_equal 1, type.type_cast(1) + assert_equal nil, type.type_cast_from_user(nil) + assert_equal "1", type.type_cast_from_user("1") + assert_equal 1, type.type_cast_from_user(1) end def test_type_cast_time type = Type::Time.new - assert_equal nil, type.type_cast(nil) - assert_equal nil, type.type_cast('') - assert_equal nil, type.type_cast('ABC') + assert_equal nil, type.type_cast_from_user(nil) + assert_equal nil, type.type_cast_from_user('') + assert_equal nil, type.type_cast_from_user('ABC') time_string = Time.now.utc.strftime("%T") - assert_equal time_string, type.type_cast(time_string).strftime("%T") + assert_equal time_string, type.type_cast_from_user(time_string).strftime("%T") end def test_type_cast_datetime_and_timestamp type = Type::DateTime.new - assert_equal nil, type.type_cast(nil) - assert_equal nil, type.type_cast('') - assert_equal nil, type.type_cast(' ') - assert_equal nil, type.type_cast('ABC') + assert_equal nil, type.type_cast_from_user(nil) + assert_equal nil, type.type_cast_from_user('') + assert_equal nil, type.type_cast_from_user(' ') + assert_equal nil, type.type_cast_from_user('ABC') datetime_string = Time.now.utc.strftime("%FT%T") - assert_equal datetime_string, type.type_cast(datetime_string).strftime("%FT%T") + assert_equal datetime_string, type.type_cast_from_user(datetime_string).strftime("%FT%T") end def test_type_cast_date type = Type::Date.new - assert_equal nil, type.type_cast(nil) - assert_equal nil, type.type_cast('') - assert_equal nil, type.type_cast(' ') - assert_equal nil, type.type_cast('ABC') + assert_equal nil, type.type_cast_from_user(nil) + assert_equal nil, type.type_cast_from_user('') + assert_equal nil, type.type_cast_from_user(' ') + assert_equal nil, type.type_cast_from_user('ABC') date_string = Time.now.utc.strftime("%F") - assert_equal date_string, type.type_cast(date_string).strftime("%F") + assert_equal date_string, type.type_cast_from_user(date_string).strftime("%F") end def test_type_cast_duration_to_integer type = Type::Integer.new - assert_equal 1800, type.type_cast(30.minutes) - assert_equal 7200, type.type_cast(2.hours) + assert_equal 1800, type.type_cast_from_user(30.minutes) + assert_equal 7200, type.type_cast_from_user(2.hours) end def test_string_to_time_with_timezone [:utc, :local].each do |zone| with_timezone_config default: zone do type = Type::DateTime.new - assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast("Wed, 04 Sep 2013 03:00:00 EAT") + assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast_from_user("Wed, 04 Sep 2013 03:00:00 EAT") end end end @@ -149,7 +149,7 @@ module ActiveRecord def test_binary_encoding type = SQLite3Binary.new utf8_string = "a string".encode(Encoding::UTF_8) - type_cast = type.type_cast(utf8_string) + type_cast = type.type_cast_from_user(utf8_string) assert_equal Encoding::ASCII_8BIT, type_cast.encoding end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index d4f8ef5b4d..9f1d110ddb 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -1,8 +1,10 @@ require 'cases/helper' require 'models/topic' +require 'models/post' +require 'models/author' class YamlSerializationTest < ActiveRecord::TestCase - fixtures :topics + fixtures :topics, :authors, :posts def test_to_yaml_with_time_with_zone_should_not_raise_exception with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do @@ -69,4 +71,15 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_not topic.new_record?, "Loaded records without ID are not new" assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization" end + + def test_types_of_virtual_columns_are_not_changed_on_round_trip + author = Author.select('authors.*, count(posts.id) as posts_count') + .joins(:posts) + .group('authors.id') + .first + dumped = YAML.load(YAML.dump(author)) + + assert_equal 5, author.posts_count + assert_equal 5, dumped.posts_count + end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 5f459cf682..98fd0b6803 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -529,6 +529,7 @@ ActiveRecord::Schema.define do t.references :best_friend t.references :best_friend_of t.integer :insures, null: false, default: 0 + t.timestamp :born_at t.timestamps end |