diff options
Diffstat (limited to 'activerecord/lib')
53 files changed, 395 insertions, 433 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 45c275a017..e576ec4d40 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -129,10 +129,10 @@ module ActiveRecord # is an instance of the value class. Specifying a custom converter allows the new value to be automatically # converted to an instance of value class if necessary. # - # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that - # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor - # for the value class is called +create+ and it expects a CIDR address string as a parameter. New - # values can be assigned to the value object using either another NetAddr::CIDR object, a string + # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html). + # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. + # New values can be assigned to the value object using either another NetAddr::CIDR object, a string # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet # these requirements: # @@ -244,6 +244,10 @@ module ActiveRecord def writer_method(name, class_name, mapping, allow_nil, converter) define_method("#{name}=") do |part| klass = class_name.constantize + if part.is_a?(Hash) + part = klass.new(*part.values) + end + unless part.is_a?(klass) || converter.nil? || part.nil? part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 572f556999..31108cc1aa 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -106,7 +106,7 @@ module ActiveRecord table, foreign_table = tables.shift, tables.first if reflection.source_macro == :belongs_to - if reflection.options[:polymorphic] + if reflection.polymorphic? key = reflection.association_primary_key(assoc_klass) else key = reflection.association_primary_key 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 35ad512537..954128064d 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -116,7 +116,7 @@ module ActiveRecord end def target_reflection_has_associated_record? - !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?) + !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?) end def update_through_counter?(method) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 01173b68f3..35659766d3 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -217,7 +217,7 @@ module ActiveRecord reflection.check_validity! reflection.check_eager_loadable! - if reflection.options[:polymorphic] + if reflection.polymorphic? raise EagerLoadPolymorphicError.new(reflection) end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 63773bd5e1..33c8619359 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -104,11 +104,11 @@ module ActiveRecord end def association_key_type - @klass.column_types[association_key_name.to_s].type + @klass.column_for_attribute(association_key_name).type end def owner_key_type - @model.column_types[owner_key_name.to_s].type + @model.column_for_attribute(owner_key_name).type end def load_slices(slices) diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index f8a85b8a6f..fcf3b219d4 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -63,14 +63,13 @@ module ActiveRecord # Note: this does not capture all cases, for example it would be crazy to try to # properly support stale-checking for nested associations. def stale_state - if through_reflection.macro == :belongs_to + if through_reflection.belongs_to? owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s end end def foreign_key_present? - through_reflection.macro == :belongs_to && - !owner[through_reflection.foreign_key].nil? + through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil? end def ensure_mutable diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index c4cf084a04..40e2918777 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -126,8 +126,8 @@ module ActiveRecord def read_value return if values.values.compact.empty? - @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name) - klass = column.klass + @column = object.column_for_attribute(name) + klass = column ? column.klass : nil if klass == Time read_time @@ -186,8 +186,7 @@ module ActiveRecord positions = (1..max_position) validate_required_parameters!(positions) - set_values = values.values_at(*positions) - klass.new(*set_values) + values.slice(*positions) end # Checks whether some blank date parameter exists. Note that this is different diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index fb6473b7c7..e626227e7e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -18,6 +18,8 @@ module ActiveRecord include TimeZoneConversion include Dirty include Serialization + + delegate :column_for_attribute, to: :class end AttrNames = Module.new { @@ -192,6 +194,26 @@ module ActiveRecord [] end end + + # Returns the column object for the named attribute. + # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the + # named attribute does not exist. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter + # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...> + # + # person.column_for_attribute(:nothing) + # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...> + def column_for_attribute(name) + name = name.to_s + columns_hash.fetch(name) do + ConnectionAdapters::NullColumn.new(name) + end + end end # If we haven't generated any methods yet, generate them, then @@ -287,11 +309,6 @@ module ActiveRecord } end - # Placeholder so it can be overriden when needed by serialization - def attributes_for_coder # :nodoc: - attributes - end - # Returns an <tt>#inspect</tt>-like string for the value of the # attribute +attr_name+. String attributes are truncated upto 50 # characters, Date and Time attributes are returned in the @@ -344,23 +361,6 @@ module ActiveRecord !value.nil? && !(value.respond_to?(:empty?) && value.empty?) end - # Returns the column object for the named attribute. Returns +nil+ if the - # named attribute not exists. - # - # class Person < ActiveRecord::Base - # end - # - # person = Person.new - # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter - # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...> - # - # person.column_for_attribute(:nothing) - # # => nil - def column_for_attribute(name) - # FIXME: should this return a null object for columns that don't exist? - self.class.columns_hash[name.to_s] - end - # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing. @@ -443,16 +443,16 @@ module ActiveRecord # Filters the primary keys and readonly attributes from the attribute names. def attributes_for_update(attribute_names) - attribute_names.select do |name| - column_for_attribute(name) && !readonly_attribute?(name) + attribute_names.reject do |name| + readonly_attribute?(name) end end # Filters out the primary keys, from the attribute names, when the primary # key is to be generated (e.g. the id attribute has no value). def attributes_for_create(attribute_names) - attribute_names.select do |name| - column_for_attribute(name) && !(pk_attribute?(name) && id.nil?) + attribute_names.reject do |name| + pk_attribute?(name) && id.nil? end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index ad01b5bf25..4e32b78e34 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -94,33 +94,7 @@ module ActiveRecord end def _field_changed?(attr, old, value) - if column = column_for_attribute(attr) - if column.number? && (changes_from_nil_to_empty_string?(column, old, value) || - changes_from_zero_to_string?(old, value)) - value = nil - else - value = column.type_cast(value) - end - end - - old != value - end - - def changes_from_nil_to_empty_string?(column, old, value) - # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. - # Hence we don't record it as a change if the value changes from nil to ''. - # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll - # be typecast back to 0 (''.to_i => 0) - column.null && (old.nil? || old == 0) && value.blank? - end - - def changes_from_zero_to_string?(old, value) - # For columns with old 0 and value non-empty string - old == 0 && value.is_a?(String) && value.present? && non_zero?(value) - end - - def non_zero?(value) - value !~ /\A0+(\.0+)?\z/ + column_for_attribute(attr).changed?(old, value) end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 99b95b1fc4..ae3785638a 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -94,7 +94,7 @@ module ActiveRecord def cacheable_column?(column) if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - ! serialized_attributes.include? column.name + true else attribute_types_cached_by_default.include?(column.type) end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index b247bf8a45..148fc9eae5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -76,21 +76,6 @@ module ActiveRecord module Behavior # :nodoc: extend ActiveSupport::Concern - module ClassMethods # :nodoc: - def initialize_attributes(attributes, options = {}) - serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized - super(attributes, options) - - serialized_attributes.each do |key, coder| - if attributes.key?(key) - attributes[key] = Type::Serialized::Attribute.new(coder, attributes[key], serialized) - end - end - - attributes - end - end - def should_record_timestamps? super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?) end @@ -98,50 +83,6 @@ module ActiveRecord def keys_for_partial_write super | (attributes.keys & self.class.serialized_attributes.keys) end - - def _field_changed?(attr, old, value) - if self.class.serialized_attributes.include?(attr) - old != value - else - super - end - end - - def read_attribute_before_type_cast(attr_name) - if self.class.serialized_attributes.include?(attr_name) - super.unserialized_value - else - super - end - end - - def attributes_before_type_cast - super.dup.tap do |attributes| - self.class.serialized_attributes.each_key do |key| - if attributes.key?(key) - attributes[key] = attributes[key].unserialized_value - end - end - end - end - - def typecasted_attribute_value(name) - if self.class.serialized_attributes.include?(name) - @raw_attributes[name].serialized_value - else - super - end - end - - def attributes_for_coder - attribute_names.each_with_object({}) do |name, attrs| - attrs[name] = if self.class.serialized_attributes.include?(name) - @raw_attributes[name].serialized_value - else - read_attribute(name) - end - 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 3b9b9c81e8..5203b30462 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -53,11 +53,11 @@ module ActiveRecord # specified +value+. Empty strings for fixnum and float columns are # turned into +nil+. def write_attribute(attr_name, value) - write_attribute_with_type_cast(attr_name, value, :type_cast_for_write) + write_attribute_with_type_cast(attr_name, value, true) end def raw_write_attribute(attr_name, value) - write_attribute_with_type_cast(attr_name, value, :raw_type_cast_for_write) + write_attribute_with_type_cast(attr_name, value, false) end private @@ -66,24 +66,26 @@ module ActiveRecord write_attribute(attribute_name, value) end - def write_attribute_with_type_cast(attr_name, value, type_cast_method) + def write_attribute_with_type_cast(attr_name, value, should_type_cast) 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) + unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" + end + # If we're dealing with a binary column, write the data to the cache # so we don't attempt to typecast multiple times. - if column && column.binary? + if column.binary? @attributes[attr_name] = value end - if column - @raw_attributes[attr_name] = column.public_send(type_cast_method, value) - elsif @raw_attributes.has_key?(attr_name) - @raw_attributes[attr_name] = value + if should_type_cast + @raw_attributes[attr_name] = column.type_cast_for_write(value) else - raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" + @raw_attributes[attr_name] = value end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index f836e60988..04ae67234f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -18,21 +18,7 @@ module ActiveRecord value = column.type_cast_for_database(value) end - case value - when String, ActiveSupport::Multibyte::Chars - "'#{quote_string(value.to_s)}'" - when true then quoted_true - when false then quoted_false - when nil then "NULL" - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s('F') - when Numeric, ActiveSupport::Duration then value.to_s - when Date, Time then "'#{quoted_date(value)}'" - when Symbol then "'#{quote_string(value.to_s)}'" - when Class then "'#{value.to_s}'" - else - "'#{quote_string(YAML.dump(value))}'" - end + _quote(value) end # Cast a +value+ to a type that the database understands. For example, @@ -52,20 +38,10 @@ module ActiveRecord value = column.type_cast_for_database(value) end - case value - when Symbol, ActiveSupport::Multibyte::Chars - value.to_s - when true then unquoted_true - when false then unquoted_false - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s('F') - when Date, Time then quoted_date(value) - when *types_which_need_no_typecasting - value - else - to_type = column ? " to #{column.type}" : "" - raise TypeError, "can't cast #{value.class}#{to_type}" - end + _type_cast(value) + rescue TypeError + to_type = column ? " to #{column.type}" : "" + raise TypeError, "can't cast #{value.class}#{to_type}" end # Quotes a string, escaping any ' (single quote) and \ (backslash) @@ -129,6 +105,39 @@ module ActiveRecord def types_which_need_no_typecasting [nil, Numeric, String] end + + def _quote(value) + case value + when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s('F') + when Numeric, ActiveSupport::Duration then value.to_s + when Date, Time then "'#{quoted_date(value)}'" + when Symbol then "'#{quote_string(value.to_s)}'" + when Class then "'#{value.to_s}'" + else + "'#{quote_string(YAML.dump(value))}'" + end + end + + def _type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s('F') + when Date, Time then quoted_date(value) + when *types_which_need_no_typecasting + value + else raise TypeError + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 117c0f0969..a9b3e9cfb9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -102,8 +102,8 @@ module ActiveRecord # * <tt>:index</tt> - # Create an index for the column. Can be either <tt>true</tt> or an options hash. # - # For clarity's sake: the precision is the number of significant digits, - # while the scale is the number of digits that can be stored following + # Note: The precision is the total number of significant digits + # and the scale is the number of digits that can be stored following # the decimal point. For example, the number 123.45 has a precision of 5 # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can # range from -999.99 to 999.99. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index b589cfee09..cc494a7f40 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -14,7 +14,10 @@ module ActiveRecord module ConnectionAdapters # :nodoc: extend ActiveSupport::Autoload - autoload :Column + autoload_at 'active_record/connection_adapters/column' do + autoload :Column + autoload :NullColumn + end autoload :ConnectionSpecification autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 2677b6ee83..759ac9943f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -218,10 +218,9 @@ module ActiveRecord # QUOTING ================================================== - def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary - s = value.unpack("H*")[0] - "x'#{s}'" + def _quote(value) # :nodoc: + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" else super end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 4fbc55a07e..5e4e00bc64 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -16,8 +16,8 @@ module ActiveRecord attr_reader :name, :default, :cast_type, :null, :sql_type, :default_function delegate :type, :precision, :scale, :limit, :klass, :accessor, - :text?, :number?, :binary?, :serialized?, - :type_cast, :type_cast_for_write, :raw_type_cast_for_write, :type_cast_for_database, + :text?, :number?, :binary?, :serialized?, :changed?, + :type_cast, :type_cast_for_write, :type_cast_for_database, :type_cast_for_schema, to: :cast_type @@ -55,6 +55,12 @@ module ActiveRecord type_cast(default) end end + + class NullColumn < Column + def initialize(name) + super name, nil, Type::Value.new + end + end end # :startdoc: end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index f7bad20f00..971f5eed7e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -6,15 +6,6 @@ module ActiveRecord "(#{point[0]},#{point[1]})" end - def string_to_bit(value) # :nodoc: - case value - when /^0x/i - value[2..-1].hex.to_s(2) # Hexadecimal notation - else - value # Bit-string notation - end - end - def hstore_to_string(object, array_member = false) # :nodoc: if Hash === object string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',') @@ -76,28 +67,6 @@ module ActiveRecord end end - def string_to_cidr(string) # :nodoc: - if string.nil? - nil - elsif String === string - begin - IPAddr.new(string) - rescue ArgumentError - nil - end - else - string - end - end - - def cidr_to_string(object) # :nodoc: - if IPAddr === object - "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}" - else - object - end - end - def string_to_array(string, oid) # :nodoc: parse_pg_array(string).map {|val| type_cast_array(oid, val)} end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 2494e19f84..33a98b4fcb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -2,6 +2,7 @@ require 'active_record/connection_adapters/postgresql/oid/infinity' require 'active_record/connection_adapters/postgresql/oid/array' require 'active_record/connection_adapters/postgresql/oid/bit' +require 'active_record/connection_adapters/postgresql/oid/bit_varying' require 'active_record/connection_adapters/postgresql/oid/bytea' require 'active_record/connection_adapters/postgresql/oid/cidr' require 'active_record/connection_adapters/postgresql/oid/date' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb index 9b2d887d07..3073f8ff30 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -2,10 +2,19 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Bit < Type::String + class Bit < Type::Value + def type + :bit + end + def type_cast(value) if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_bit value + case value + when /^0x/i + value[2..-1].hex.to_s(2) # Hexadecimal notation + else + value # Bit-string notation + end else value end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb new file mode 100644 index 0000000000..054af285bb --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb @@ -0,0 +1,13 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class BitVarying < OID::Bit + def type + :bit_varying + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb index 158468fe5b..534961a414 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -18,8 +18,26 @@ module ActiveRecord end end + def type_cast_for_database(value) + if IPAddr === value + "#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}" + else + value + end + end + def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_cidr value + if value.nil? + nil + elsif String === value + begin + IPAddr.new(value) + rescue ArgumentError + nil + end + else + value + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb index 9ccbf71159..34e2276dd1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -11,7 +11,8 @@ module ActiveRecord when 'infinity' then ::Float::INFINITY when '-infinity' then -::Float::INFINITY when / BC$/ - super("-" + value.sub(/ BC$/, "")) + astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) + super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb index 9753d71461..77dd97e140 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb @@ -5,9 +5,8 @@ module ActiveRecord class Float < Type::Float include Infinity - def type_cast(value) + def cast_value(value) case value - when nil then nil when 'Infinity' then ::Float::INFINITY when '-Infinity' then -::Float::INFINITY when 'NaN' then ::Float::NAN 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 bf680b6624..a65ca83f77 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -8,12 +8,6 @@ module ActiveRecord end def type_cast_for_write(value) - # roundtrip to ensure uniform uniform types - # TODO: This is not an efficient solution. - cast_value(type_cast_for_database(value)) - end - - def type_cast_for_database(value) ConnectionAdapters::PostgreSQLColumn.hstore_to_string(value) end 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 42a5110ffd..c87422fe32 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -8,12 +8,6 @@ module ActiveRecord end def type_cast_for_write(value) - # roundtrip to ensure uniform uniform types - # TODO: This is not an efficient solution. - cast_value(type_cast_for_database(value)) - end - - def type_cast_for_database(value) ConnectionAdapters::PostgreSQLColumn.json_to_string(value) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 697dceb7c2..d25eb256c2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -7,6 +7,10 @@ module ActiveRecord class_attribute :precision + def type + :money + end + def scale 2 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb index f9531ddee3..9007bfb178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -2,7 +2,11 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Point < Type::String + class Point < Type::Value + def type + :point + end + def type_cast(value) if ::String === value if value[0] == '(' && value[-1] == ')' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 7b7144a3a8..3cf40e6cd4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -44,11 +44,6 @@ module ActiveRecord when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end - when IPAddr - case sql_type - when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) - else super - end when Float if value.infinite? && column.type == :datetime "'#{value.to_s.downcase}'" @@ -66,7 +61,6 @@ module ActiveRecord end when String case sql_type - when 'bytea' then "'#{escape_bytea(value)}'" when 'xml' then "xml '#{quote_string(value)}'" when /^bit/ case value @@ -110,27 +104,12 @@ module ActiveRecord super(value, column) end end - when String - if 'bytea' == column.sql_type - # Return a bind param hash with format as binary. - # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc - # for more information - { value: value, format: 1 } - else - super(value, column) - end when Hash case column.sql_type when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member) when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end - when IPAddr - if %w(inet cidr).include? column.sql_type - PostgreSQLColumn.cidr_to_string(value) - else - super(value, column) - end else super(value, column) end @@ -170,8 +149,9 @@ module ActiveRecord result = "#{result}.#{sprintf("%06d", value.usec)}" end - if value.year < 0 - result = result.sub(/^-/, "") + " BC" + if value.year <= 0 + bce_year = format("%04d", -value.year + 1) + result = result.sub(/^-?\d+/, bce_year) + " BC" end result end @@ -184,6 +164,27 @@ module ActiveRecord quote(value, column) end end + + private + + def _quote(value) + if value.is_a?(Type::Binary::Data) + "'#{escape_bytea(value.to_s)}'" + else + super + end + end + + def _type_cast(value) + if value.is_a?(Type::Binary::Data) + # Return a bind param hash with format as binary. + # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + else + super + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index bcfd605165..0867e5ef54 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -4,68 +4,84 @@ module ActiveRecord module ColumnMethods def xml(*args) options = args.extract_options! - column(args[0], 'xml', options) + column(args[0], :xml, options) end def tsvector(*args) options = args.extract_options! - column(args[0], 'tsvector', options) + column(args[0], :tsvector, options) end def int4range(name, options = {}) - column(name, 'int4range', options) + column(name, :int4range, options) end def int8range(name, options = {}) - column(name, 'int8range', options) + column(name, :int8range, options) end def tsrange(name, options = {}) - column(name, 'tsrange', options) + column(name, :tsrange, options) end def tstzrange(name, options = {}) - column(name, 'tstzrange', options) + column(name, :tstzrange, options) end def numrange(name, options = {}) - column(name, 'numrange', options) + column(name, :numrange, options) end def daterange(name, options = {}) - column(name, 'daterange', options) + column(name, :daterange, options) end def hstore(name, options = {}) - column(name, 'hstore', options) + column(name, :hstore, options) end def ltree(name, options = {}) - column(name, 'ltree', options) + column(name, :ltree, options) end def inet(name, options = {}) - column(name, 'inet', options) + column(name, :inet, options) end def cidr(name, options = {}) - column(name, 'cidr', options) + column(name, :cidr, options) end def macaddr(name, options = {}) - column(name, 'macaddr', options) + column(name, :macaddr, options) end def uuid(name, options = {}) - column(name, 'uuid', options) + column(name, :uuid, options) end def json(name, options = {}) - column(name, 'json', options) + column(name, :json, options) end def citext(name, options = {}) - column(name, 'citext', options) + column(name, :citext, options) + end + + def point(name, options = {}) + column(name, :point, options) + end + + def bit(name, options) + column(name, :bit, options) + end + + def bit_varying(name, options) + column(name, :bit_varying, options) + end + + def money(name, options) + column(name, :money, options) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 23c1a8de2f..283ca81f94 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -103,7 +103,11 @@ module ActiveRecord uuid: { name: "uuid" }, json: { name: "json" }, ltree: { name: "ltree" }, - citext: { name: "citext" } + citext: { name: "citext" }, + point: { name: "point" }, + bit: { name: "bit" }, + bit_varying: { name: "bit varying" }, + money: { name: "money" }, } OID = PostgreSQL::OID #:nodoc: @@ -432,8 +436,8 @@ module ActiveRecord m.alias_type 'name', 'varchar' m.alias_type 'bpchar', 'varchar' m.register_type 'bool', Type::Boolean.new - m.register_type 'bit', OID::Bit.new - m.alias_type 'varbit', 'bit' + register_class_with_limit m, 'bit', OID::Bit + register_class_with_limit m, 'varbit', OID::BitVarying m.alias_type 'timestamptz', 'timestamp' m.register_type 'date', OID::Date.new m.register_type 'time', OID::Time.new @@ -499,64 +503,16 @@ module ActiveRecord # Extracts the value from a PostgreSQL column default definition. def extract_value_from_default(oid, default) # :nodoc: - # This is a performance optimization for Ruby 1.9.2 in development. - # If the value is nil, we return nil straight away without checking - # the regular expressions. If we check each regular expression, - # Regexp#=== will call NilClass#to_str, which will trigger - # method_missing (defined by whiny nil in ActiveSupport) which - # makes this method very very slow. - return default unless default - - # TODO: The default extraction is related to the cast-type. - # we should probably make a type_map lookup and cast the default- - # expression accordingly - if oid.type == :enum && default =~ /\A'(.*)'::/ - return $1 - end - case default - when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m - $1 + # Quoted types + when /\A[\(B]?'(.*)'::/m + $1.gsub(/''/, "'") + # Boolean types + when 'true', 'false' + default # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ $1 - # Character types - when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m - $1.gsub(/''/, "'") - # Binary data types - when /\A'(.*)'::bytea\z/m - $1 - # Date/time types - when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ - $1 - when /\A'(.*)'::interval\z/ - $1 - # Boolean type - when 'true' - true - when 'false' - false - # Geometric types - when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ - $1 - # Network address types - when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ - $1 - # Bit string types - when /\AB'(.*)'::"?bit(?: varying)?"?\z/ - $1 - # XML type - when /\A'(.*)'::xml\z/m - $1 - # Arrays - when /\A'(.*)'::"?\D+"?\[\]\z/ - $1 - # Hstore - when /\A'(.*)'::hstore\z/ - $1 - # JSON - when /\A'(.*)'::json\z/ - $1 # Object identifier types when /\A-?\d+\z/ $1 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index adf893d7e7..e6163771e8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -219,10 +219,9 @@ module ActiveRecord # QUOTING ================================================== - def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary - s = value.unpack("H*")[0] - "x'#{s}'" + def _quote(value) # :nodoc: + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" else super end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index e99e235fe8..c996e93076 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -249,10 +249,10 @@ module ActiveRecord # # Instantiates a single new object # User.new(first_name: 'Jamie') def initialize(attributes = nil, options = {}) - defaults = self.class.column_defaults.dup + defaults = self.class.raw_column_defaults.dup defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? } - @raw_attributes = self.class.initialize_attributes(defaults) + @raw_attributes = defaults @column_types_override = nil @column_types = self.class.column_types @@ -278,13 +278,14 @@ module ActiveRecord # post.init_with('attributes' => { 'title' => 'hello world' }) # post.title # => 'hello world' def init_with(coder) - @raw_attributes = self.class.initialize_attributes(coder['attributes']) + @raw_attributes = coder['raw_attributes'] @column_types_override = coder['column_types'] @column_types = self.class.column_types init_internals - @new_record = false + @attributes = coder['attributes'] if coder['attributes'] + @new_record = coder['new_record'] self.class.define_attribute_methods @@ -323,16 +324,16 @@ module ActiveRecord ## def initialize_dup(other) # :nodoc: cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) - self.class.initialize_attributes(cloned_attributes, :serialized => false) @raw_attributes = cloned_attributes @raw_attributes[self.class.primary_key] = nil + @attributes = other.clone_attributes(:read_attribute) + @attributes[self.class.primary_key] = nil run_callbacks(:initialize) unless _initialize_callbacks.empty? @aggregation_cache = {} @association_cache = {} - @attributes = {} @new_record = true @destroyed = false @@ -353,7 +354,9 @@ module ActiveRecord # Post.new.encode_with(coder) # coder # => {"attributes" => {"id" => nil, ... }} def encode_with(coder) - coder['attributes'] = attributes_for_coder + coder['raw_attributes'] = @raw_attributes + coder['attributes'] = @attributes + coder['new_record'] = new_record? end # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ @@ -432,6 +435,29 @@ module ActiveRecord "#<#{self.class} #{inspection}>" end + # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record` + # when pp is required. + def pretty_print(pp) + pp.object_address_group(self) do + if defined?(@attributes) && @attributes + column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } + pp.seplist(column_names, proc { pp.text ',' }) do |column_name| + column_value = read_attribute(column_name) + pp.breakable ' ' + pp.group(1) do + pp.text column_name + pp.text ':' + pp.breakable + pp.pp column_value + end + end + else + pp.breakable ' ' + pp.text 'not initialized' + end + end + end + # Returns a hash of the given methods with their names as keys and returned values as values. def slice(*methods) Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 71d1e85b0d..6ba667b996 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -657,7 +657,7 @@ module ActiveRecord fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s if association.name.to_s != fk_name && value = row.delete(association.name.to_s) - if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") + if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") # support polymorphic belongs_to as "label (Type)" row[association.foreign_type] = $1 end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 8fe32bcb6c..481e5c17e4 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -711,7 +711,7 @@ module ActiveRecord if ActiveRecord::Base.timestamped_migrations [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max else - "%.3d" % number + SchemaMigration.normalize_migration_number(number) end end @@ -851,19 +851,6 @@ module ActiveRecord migrations(migrations_paths).last || NullMigration.new end - def proper_table_name(name, options = {}) - ActiveSupport::Deprecation.warn "ActiveRecord::Migrator.proper_table_name is deprecated and will be removed in Rails 4.2. Use the proper_table_name instance method on ActiveRecord::Migration instead" - options = { - table_name_prefix: ActiveRecord::Base.table_name_prefix, - table_name_suffix: ActiveRecord::Base.table_name_suffix - }.merge(options) - if name.respond_to? :table_name - name.table_name - else - "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" - end - end - def migrations_paths @migrations_paths ||= ['db/migrate'] # just to not break things if someone uses: migration_path = some_string diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ad6428d8a8..baf2b5fbf8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -241,6 +241,14 @@ module ActiveRecord @column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }] end + # Returns a hash where the keys are the column names and the values + # 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)] + }] + end + # Returns an array of column names as strings. def column_names @column_names ||= columns.map { |column| column.name } @@ -285,6 +293,7 @@ module ActiveRecord @arel_engine = nil @column_defaults = nil + @raw_column_defauts = nil @column_names = nil @column_types = nil @content_columns = nil @@ -295,13 +304,6 @@ module ActiveRecord @cached_time_zone = nil end - # This is a hook for use by modules that need to do extra stuff to - # attributes when they are initialized. (e.g. attribute - # serialization) - def initialize_attributes(attributes, options = {}) #:nodoc: - attributes - end - private # Guesses the table name, but does not decorate it with prefix and suffix information. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 78ae05073a..f1f0d3e57f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -49,7 +49,11 @@ module ActiveRecord def instantiate(attributes, column_types = {}) klass = discriminate_class_for_record(attributes) column_types = klass.decorate_columns(column_types.dup) - klass.allocate.init_with('attributes' => attributes, 'column_types' => column_types) + klass.allocate.init_with( + 'raw_attributes' => attributes, + 'column_types' => column_types, + 'new_record' => false, + ) end private diff --git a/activerecord/lib/active_record/properties.rb b/activerecord/lib/active_record/properties.rb index e70f84808d..48ee42aaca 100644 --- a/activerecord/lib/active_record/properties.rb +++ b/activerecord/lib/active_record/properties.rb @@ -5,7 +5,7 @@ module ActiveRecord Type = ActiveRecord::Type included do - class_attribute :user_provided_columns, instance_accessor: false # :internal + class_attribute :user_provided_columns, instance_accessor: false # :internal: self.user_provided_columns = {} end @@ -113,6 +113,7 @@ module ActiveRecord @columns_hash = nil @column_types = nil @column_defaults = nil + @raw_column_defaults = nil @column_names = nil @content_columns = nil end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index fa25ceaefa..6dca206f2a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -86,14 +86,15 @@ db_namespace = namespace :db do abort 'Schema migrations table does not exist yet.' end db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}") - db_list.map! { |version| "%.3d" % version } + db_list.map! { |version| ActiveRecord::SchemaMigration.normalize_migration_number(version) } file_list = [] ActiveRecord::Migrator.migrations_paths.each do |path| Dir.foreach(path) do |file| # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern if match_data = /^(\d{3,})_(.+)\.rb$/.match(file) - status = db_list.delete(match_data[1]) ? 'up' : 'down' - file_list << [status, match_data[1], match_data[2].humanize] + version = ActiveRecord::SchemaMigration.normalize_migration_number(match_data[1]) + status = db_list.delete(version) ? 'up' : 'down' + file_list << [status, version, match_data[2].humanize] end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index dd80ec6274..4d5203612c 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -239,7 +239,7 @@ module ActiveRecord def association_scope_cache(conn, owner) key = conn.prepared_statements - if options[:polymorphic] + if polymorphic? key = [key, owner.read_attribute(@foreign_type)] end @association_scope_cache[key] ||= @scope_lock.synchronize { @@ -303,7 +303,7 @@ module ActiveRecord end def check_validity_of_inverse! - unless options[:polymorphic] + unless polymorphic? if has_inverse? && inverse_of.nil? raise InverseOfAssociationNotFoundError.new(self) end @@ -403,7 +403,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi def association_class case macro when :belongs_to - if options[:polymorphic] + if polymorphic? Associations::BelongsToPolymorphicAssociation else Associations::BelongsToAssociation @@ -424,7 +424,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi end def polymorphic? - options.key? :polymorphic + options[:polymorphic] end VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] @@ -441,7 +441,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi def calculate_constructable(macro, options) case macro when :belongs_to - !options[:polymorphic] + !polymorphic? when :has_one !options[:through] else @@ -723,7 +723,7 @@ directive on your declaration like: raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) end - if through_reflection.options[:polymorphic] + if through_reflection.polymorphic? raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) end @@ -731,11 +731,11 @@ directive on your declaration like: raise HasManyThroughSourceAssociationNotFoundError.new(self) end - if options[:source_type] && source_reflection.options[:polymorphic].nil? + if options[:source_type] && !source_reflection.polymorphic? raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) end - if source_reflection.options[:polymorphic] && options[:source_type].nil? + if source_reflection.polymorphic? && options[:source_type].nil? raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d92ff781ee..cef40be7ce 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -74,7 +74,14 @@ module ActiveRecord def _update_record(values, id, id_was) # :nodoc: substitutes, binds = substitute_values values - um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key) + + scope = @klass.unscoped + + if @klass.finder_needs_type_condition? + scope.unscope!(where: @klass.inheritance_column) + end + + um = scope.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key) @klass.connection.update( um, diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index d155517b18..11ab1b4595 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -177,7 +177,7 @@ module ActiveRecord end result = result.map do |attributes| - values = klass.initialize_attributes(attributes).values + values = attributes.values columns.zip(values).map { |column, value| column.type_cast value } end @@ -278,7 +278,7 @@ module ActiveRecord if group_attrs.first.respond_to?(:to_sym) association = @klass._reflect_on_association(group_attrs.first.to_sym) - associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations + associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations group_fields = Array(associated ? association.foreign_key : group_attrs) else group_fields = group_attrs diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index a9d164e366..3a004d58c9 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -5,6 +5,9 @@ require 'active_record/base' module ActiveRecord class SchemaMigration < ActiveRecord::Base class << self + def primary_key + nil + end def table_name "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" @@ -36,6 +39,10 @@ module ActiveRecord connection.drop_table(table_name) end end + + def normalize_migration_number(number) + "%.3d" % number.to_i + end end def version diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 1a766093d0..019fe2218e 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -180,12 +180,12 @@ module ActiveRecord #:nodoc: class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class - type = if klass.serialized_attributes.key?(name) + column = klass.columns_hash[name] || Type::Value.new + + type = if column.serialized? super - elsif klass.columns_hash.key?(name) - klass.columns_hash[name].type else - NilClass + column.type end { :text => :string, diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb index e34b7bb268..bc93f6e1bf 100644 --- a/activerecord/lib/active_record/type/binary.rb +++ b/activerecord/lib/active_record/type/binary.rb @@ -9,8 +9,22 @@ module ActiveRecord true end - def klass - ::String + def type_cast_for_database(value) + Data.new(super) + end + + class Data + def initialize(value) + @value = value + end + + def to_s + @value + end + + def hex + @value.unpack('H*')[0] + end end end end diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb index 6eed005345..a9db51c6ba 100644 --- a/activerecord/lib/active_record/type/decimal.rb +++ b/activerecord/lib/active_record/type/decimal.rb @@ -7,10 +7,6 @@ module ActiveRecord :decimal end - def klass - ::BigDecimal - end - def type_cast_for_schema(value) value.to_s end diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb index dc50dae328..42eb44b9a9 100644 --- a/activerecord/lib/active_record/type/float.rb +++ b/activerecord/lib/active_record/type/float.rb @@ -7,10 +7,6 @@ module ActiveRecord :float end - def klass - ::Float - end - alias type_cast_for_database type_cast private diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index 1e2147dec9..08477d1303 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -7,10 +7,6 @@ module ActiveRecord :integer end - def klass - ::Fixnum - end - alias type_cast_for_database type_cast private diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb index 464d631d80..d5cb13233c 100644 --- a/activerecord/lib/active_record/type/numeric.rb +++ b/activerecord/lib/active_record/type/numeric.rb @@ -5,14 +5,38 @@ module ActiveRecord true end - def type_cast_for_write(value) - case value - when true then 1 - when false then 0 - when ::String then value.presence - else super + def type_cast(value) + value = case value + when true then 1 + when false then 0 + when ::String then value.presence + else value + end + super(value) + end + + def changed?(old_value, new_value) # :nodoc: + # 0 => 'wibble' should mark as changed so numericality validations run + if nil_or_zero?(old_value) && non_numeric_string?(new_value) + # nil => '' should not mark as changed + old_value != new_value.presence + else + super end end + + private + + def non_numeric_string?(value) + # 'wibble'.to_i will give zero, we want to make sure + # that we aren't marking int zero to string zero as + # changed. + value !~ /\A\d+\.?\d*\z/ + end + + def nil_or_zero?(value) + value.nil? || value == 0 + end end end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 4052ac0fa0..94d9d9a549 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -10,20 +10,21 @@ module ActiveRecord end def type_cast(value) - if value.respond_to?(:unserialized_value) - value.unserialized_value(super(value.value)) + if is_default_value?(value) + value else - super + coder.load(super) end end def type_cast_for_write(value) - Attribute.new(coder, value, :unserialized) + return if value.nil? + unless is_default_value?(value) + super coder.dump(value) + end end - def raw_type_cast_for_write(value) - Attribute.new(coder, value, :serialized) - end + alias type_cast_for_database type_cast_for_write def serialized? true @@ -33,24 +34,14 @@ module ActiveRecord ActiveRecord::Store::IndifferentHashAccessor end - class Attribute < Struct.new(:coder, :value, :state) # :nodoc: - def unserialized_value(v = value) - state == :serialized ? unserialize(v) : value - end - - def serialized_value - state == :unserialized ? serialize : value - end + private - def unserialize(v) - self.state = :unserialized - self.value = coder.load(v) - end + def changed?(old_value, new_value) # :nodoc: + old_value != new_value + end - def serialize - self.state = :serialized - self.value = coder.dump(value) - end + def is_default_value?(value) + value == coder.load(nil) end end end diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb index b3f6ca7691..3b1554bd5a 100644 --- a/activerecord/lib/active_record/type/string.rb +++ b/activerecord/lib/active_record/type/string.rb @@ -9,10 +9,6 @@ module ActiveRecord true end - def klass - ::String - end - private def cast_value(value) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 9c1e9dc01e..4bc3086db3 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -12,8 +12,8 @@ module ActiveRecord @limit = options[:limit] end - # The simplified that this object represents. Subclasses - # should override this method. + # The simplified type that this object represents. Subclasses + # must override this method. def type; end # Takes an input from the database, or from attribute setters, @@ -48,13 +48,20 @@ module ActiveRecord end def klass # :nodoc: - ::Object end def type_cast_for_write(value) # :nodoc: value end - alias_method :raw_type_cast_for_write, :type_cast_for_write # :internal: + + # +old_value+ will always be type-cast. + # +new_value+ will come straight from the database + # or from assignment, so it could be anything. Types + # which cannot typecast arbitrary values should override + # this method. + def changed?(old_value, new_value) # :nodoc: + old_value != type_cast(new_value) + end private diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index b6fccc9b94..2e7b1d7206 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -14,7 +14,6 @@ module ActiveRecord finder_class = find_finder_class_for(record) table = finder_class.arel_table value = map_enum_attribute(finder_class, attribute, value) - value = deserialize_attribute(record, attribute, value) relation = build_relation(finder_class, table, attribute, value) relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted? @@ -86,12 +85,6 @@ module ActiveRecord relation end - def deserialize_attribute(record, attribute, value) - coder = record.class.serialized_attributes[attribute.to_s] - value = coder.dump value if value && coder - value - end - def map_enum_attribute(klass, attribute, value) mapping = klass.defined_enums[attribute.to_s] value = mapping[value] if value && mapping |