diff options
Diffstat (limited to 'activerecord/lib')
17 files changed, 182 insertions, 104 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index ad01b5bf25..6cd4e43ddd 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -94,33 +94,8 @@ 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 = column_for_attribute(attr) || Type::Value.new + column.changed?(old, value) end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 425c33f2c6..148fc9eae5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -83,14 +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 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_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 4d0d7ca00d..3b0dcbc6a7 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -16,7 +16,7 @@ module ActiveRecord attr_reader :name, :default, :cast_type, :null, :sql_type, :default_function delegate :type, :precision, :scale, :limit, :klass, :accessor, - :text?, :number?, :binary?, :serialized?, + :text?, :number?, :binary?, :serialized?, :changed?, :type_cast, :type_cast_for_write, :type_cast_for_database, :type_cast_for_schema, to: :cast_type 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 dc077993c5..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,7 +2,11 @@ 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 case value 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/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/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index c875bc5162..4c719b834f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -61,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 @@ -105,15 +104,6 @@ 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) @@ -173,6 +163,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 deaea12408..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,72 +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) + 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 61694674ab..67570dad3c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -104,7 +104,10 @@ module ActiveRecord json: { name: "json" }, ltree: { name: "ltree" }, citext: { name: "citext" }, - point: { name: "point"} + point: { name: "point" }, + bit: { name: "bit" }, + bit_varying: { name: "bit varying" }, + money: { name: "money" }, } OID = PostgreSQL::OID #:nodoc: @@ -433,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 @@ -558,6 +561,8 @@ module ActiveRecord # JSON when /\A'(.*)'::json\z/ $1 + when /\A'(.*)'::money\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/type/binary.rb b/activerecord/lib/active_record/type/binary.rb index e34b7bb268..9d10c91fc1 100644 --- a/activerecord/lib/active_record/type/binary.rb +++ b/activerecord/lib/active_record/type/binary.rb @@ -12,6 +12,24 @@ module ActiveRecord def klass ::String end + + 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 end diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb index 464d631d80..9cc6411e77 100644 --- a/activerecord/lib/active_record/type/numeric.rb +++ b/activerecord/lib/active_record/type/numeric.rb @@ -13,6 +13,29 @@ module ActiveRecord else super end 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 eac31f6cc3..78a6d31e26 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -36,6 +36,10 @@ module ActiveRecord private + def changed?(old_value, new_value) # :nodoc: + old_value != new_value + end + def is_default_value?(value) value == coder.load(nil) end diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 1f7d4e20b2..c072c1e2b6 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -55,6 +55,15 @@ module ActiveRecord 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 + # which cannot typecast arbitrary values should override + # this method. + def changed?(old_value, new_value) # :nodoc: + old_value != type_cast(new_value) + end + private # Responsible for casting values from external sources to the appropriate |