From 9cc8c6f3730df3d94c81a55be9ee1b7b4ffd29f6 Mon Sep 17 00:00:00 2001 From: Kir Shatrov Date: Mon, 7 Sep 2015 21:20:15 +0300 Subject: Move ActiveRecord::Type to ActiveModel The first step of bringing typecasting to ActiveModel --- .../lib/active_record/attribute_methods/query.rb | 2 +- .../active_record/connection_adapters/column.rb | 7 -- activerecord/lib/active_record/type.rb | 66 +++++++++---- .../type/adapter_specific_registry.rb | 39 +++----- activerecord/lib/active_record/type/big_integer.rb | 13 --- activerecord/lib/active_record/type/binary.rb | 50 ---------- activerecord/lib/active_record/type/boolean.rb | 19 ---- activerecord/lib/active_record/type/date.rb | 49 ---------- activerecord/lib/active_record/type/date_time.rb | 44 --------- activerecord/lib/active_record/type/decimal.rb | 50 ---------- .../active_record/type/decimal_without_scale.rb | 11 --- activerecord/lib/active_record/type/float.rb | 25 ----- .../lib/active_record/type/hash_lookup_type_map.rb | 23 ----- activerecord/lib/active_record/type/helpers.rb | 4 - .../type/helpers/accepts_multiparameter_time.rb | 30 ------ .../lib/active_record/type/helpers/mutable.rb | 18 ---- .../lib/active_record/type/helpers/numeric.rb | 34 ------- .../lib/active_record/type/helpers/time_value.rb | 58 ------------ activerecord/lib/active_record/type/integer.rb | 66 ------------- .../active_record/type/internal/abstract_json.rb | 4 +- .../lib/active_record/type/internal/timezone.rb | 15 +++ activerecord/lib/active_record/type/serialized.rb | 4 +- activerecord/lib/active_record/type/string.rb | 36 ------- activerecord/lib/active_record/type/text.rb | 11 --- activerecord/lib/active_record/type/time.rb | 42 --------- activerecord/lib/active_record/type/type_map.rb | 64 ------------- .../lib/active_record/type/unsigned_integer.rb | 15 --- activerecord/lib/active_record/type/value.rb | 104 --------------------- 28 files changed, 78 insertions(+), 825 deletions(-) delete mode 100644 activerecord/lib/active_record/type/big_integer.rb delete mode 100644 activerecord/lib/active_record/type/binary.rb delete mode 100644 activerecord/lib/active_record/type/boolean.rb delete mode 100644 activerecord/lib/active_record/type/date.rb delete mode 100644 activerecord/lib/active_record/type/date_time.rb delete mode 100644 activerecord/lib/active_record/type/decimal.rb delete mode 100644 activerecord/lib/active_record/type/decimal_without_scale.rb delete mode 100644 activerecord/lib/active_record/type/float.rb delete mode 100644 activerecord/lib/active_record/type/hash_lookup_type_map.rb delete mode 100644 activerecord/lib/active_record/type/helpers.rb delete mode 100644 activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb delete mode 100644 activerecord/lib/active_record/type/helpers/mutable.rb delete mode 100644 activerecord/lib/active_record/type/helpers/numeric.rb delete mode 100644 activerecord/lib/active_record/type/helpers/time_value.rb delete mode 100644 activerecord/lib/active_record/type/integer.rb create mode 100644 activerecord/lib/active_record/type/internal/timezone.rb delete mode 100644 activerecord/lib/active_record/type/string.rb delete mode 100644 activerecord/lib/active_record/type/text.rb delete mode 100644 activerecord/lib/active_record/type/time.rb delete mode 100644 activerecord/lib/active_record/type/type_map.rb delete mode 100644 activerecord/lib/active_record/type/unsigned_integer.rb delete mode 100644 activerecord/lib/active_record/type/value.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index 553122a5fc..10498f4322 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -19,7 +19,7 @@ module ActiveRecord if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? else - return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) + return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value) !value.blank? end elsif value.respond_to?(:zero?) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 8222811e0c..5e31efec4a 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -5,13 +5,6 @@ module ActiveRecord module ConnectionAdapters # An abstract definition of a column in a table. class Column - FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set - - module Format - ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ - ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - end - attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 53f3b53bec..165043021b 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -1,26 +1,28 @@ -require 'active_record/type/helpers' -require 'active_record/type/value' +require 'active_model/type/helpers' +require 'active_model/type/value' -require 'active_record/type/big_integer' -require 'active_record/type/binary' -require 'active_record/type/boolean' -require 'active_record/type/date' -require 'active_record/type/date_time' -require 'active_record/type/decimal' -require 'active_record/type/decimal_without_scale' -require 'active_record/type/float' -require 'active_record/type/integer' -require 'active_record/type/serialized' -require 'active_record/type/string' -require 'active_record/type/text' -require 'active_record/type/time' -require 'active_record/type/unsigned_integer' +require 'active_model/type/big_integer' +require 'active_model/type/binary' +require 'active_model/type/boolean' +require 'active_model/type/date' +require 'active_model/type/date_time' +require 'active_model/type/decimal' +require 'active_model/type/decimal_without_scale' +require 'active_model/type/float' +require 'active_model/type/integer' +require 'active_model/type/string' +require 'active_model/type/text' +require 'active_model/type/time' +require 'active_model/type/unsigned_integer' -require 'active_record/type/adapter_specific_registry' -require 'active_record/type/type_map' -require 'active_record/type/hash_lookup_type_map' +require 'active_model/type/registry' +require 'active_model/type/type_map' +require 'active_model/type/hash_lookup_type_map' require 'active_record/type/internal/abstract_json' +require 'active_record/type/internal/timezone' +require 'active_record/type/serialized' +require 'active_record/type/adapter_specific_registry' module ActiveRecord module Type @@ -53,6 +55,32 @@ module ActiveRecord end end + class Date < ActiveModel::Type::Date + include Internal::Timezone + end + + class DateTime < ActiveModel::Type::DateTime + include Internal::Timezone + end + class Time < ActiveModel::Type::Time + include Internal::Timezone + end + + Helpers = ActiveModel::Type::Helpers + BigInteger = ActiveModel::Type::BigInteger + Binary = ActiveModel::Type::Binary + Boolean = ActiveModel::Type::Boolean + Decimal = ActiveModel::Type::Decimal + DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale + Float = ActiveModel::Type::Float + Integer = ActiveModel::Type::Integer + String = ActiveModel::Type::String + Text = ActiveModel::Type::Text + UnsignedInteger = ActiveModel::Type::UnsignedInteger + Value = ActiveModel::Type::Value + TypeMap = ActiveModel::Type::TypeMap + HashLookupTypeMap = ActiveModel::Type::HashLookupTypeMap + register(:big_integer, Type::BigInteger, override: false) register(:binary, Type::Binary, override: false) register(:boolean, Type::Boolean, override: false) diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb index 5f71b3cb94..3509429058 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -1,35 +1,18 @@ +require 'active_model/type/registry' + module ActiveRecord # :stopdoc: module Type - class AdapterSpecificRegistry - def initialize - @registrations = [] - end - - def register(type_name, klass = nil, **options, &block) - block ||= proc { |_, *args| klass.new(*args) } - registrations << Registration.new(type_name, block, **options) - end - - def lookup(symbol, *args) - registration = registrations - .select { |r| r.matches?(symbol, *args) } - .max - - if registration - registration.call(self, symbol, *args) - else - raise ArgumentError, "Unknown type #{symbol.inspect}" - end + class AdapterSpecificRegistry < ActiveModel::Type::Registry + private + + def registration_klass + Registration end - - def add_modifier(options, klass, **args) - registrations << DecorationRegistration.new(options, klass, **args) + + def decoration_registration_klass + DecorationRegistration end - - protected - - attr_reader :registrations end class Registration @@ -135,7 +118,7 @@ module ActiveRecord end end - class TypeConflictError < StandardError + class TypeConflictError < ::ActiveModel::TypeConflictError end # :startdoc: diff --git a/activerecord/lib/active_record/type/big_integer.rb b/activerecord/lib/active_record/type/big_integer.rb deleted file mode 100644 index 0c72d8914f..0000000000 --- a/activerecord/lib/active_record/type/big_integer.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_record/type/integer' - -module ActiveRecord - module Type - class BigInteger < Integer # :nodoc: - private - - def max_value - ::Float::INFINITY - end - end - end -end diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb deleted file mode 100644 index 0baf8c63ad..0000000000 --- a/activerecord/lib/active_record/type/binary.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveRecord - module Type - class Binary < Value # :nodoc: - def type - :binary - end - - def binary? - true - end - - def cast(value) - if value.is_a?(Data) - value.to_s - else - super - end - end - - def serialize(value) - return if value.nil? - Data.new(super) - end - - def changed_in_place?(raw_old_value, value) - old_value = deserialize(raw_old_value) - old_value != value - end - - class Data # :nodoc: - def initialize(value) - @value = value.to_s - end - - def to_s - @value - end - alias_method :to_str, :to_s - - def hex - @value.unpack('H*')[0] - end - - def ==(other) - other == to_s || super - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb deleted file mode 100644 index f6a75512fd..0000000000 --- a/activerecord/lib/active_record/type/boolean.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveRecord - module Type - class Boolean < Value # :nodoc: - def type - :boolean - end - - private - - def cast_value(value) - if value == '' - nil - else - !ConnectionAdapters::Column::FALSE_VALUES.include?(value) - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb deleted file mode 100644 index 3ceab59ebb..0000000000 --- a/activerecord/lib/active_record/type/date.rb +++ /dev/null @@ -1,49 +0,0 @@ -module ActiveRecord - module Type - class Date < Value # :nodoc: - include Helpers::AcceptsMultiparameterTime.new - - def type - :date - end - - def type_cast_for_schema(value) - "'#{value.to_s(:db)}'" - end - - private - - def cast_value(value) - if value.is_a?(::String) - return if value.empty? - fast_string_to_date(value) || fallback_string_to_date(value) - elsif value.respond_to?(:to_date) - value.to_date - else - value - end - end - - def fast_string_to_date(string) - if string =~ ConnectionAdapters::Column::Format::ISO_DATE - new_date $1.to_i, $2.to_i, $3.to_i - end - end - - def fallback_string_to_date(string) - new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) - end - - def new_date(year, mon, mday) - if year && year != 0 - ::Date.new(year, mon, mday) rescue nil - end - end - - def value_from_multiparameter_assignment(*) - time = super - time && time.to_date - end - end - end -end diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb deleted file mode 100644 index a5199959b9..0000000000 --- a/activerecord/lib/active_record/type/date_time.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActiveRecord - module Type - class DateTime < Value # :nodoc: - include Helpers::TimeValue - include Helpers::AcceptsMultiparameterTime.new( - defaults: { 4 => 0, 5 => 0 } - ) - - def type - :datetime - end - - private - - def cast_value(string) - return string unless string.is_a?(::String) - return if string.empty? - - fast_string_to_time(string) || fallback_string_to_time(string) - end - - # '0.123456' -> 123456 - # '1.123456' -> 123456 - def microseconds(time) - time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 - end - - def fallback_string_to_time(string) - time_hash = ::Date._parse(string) - time_hash[:sec_fraction] = microseconds(time_hash) - - new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) - end - - def value_from_multiparameter_assignment(values_hash) - missing_parameter = (1..3).detect { |key| !values_hash.key?(key) } - if missing_parameter - raise ArgumentError, missing_parameter - end - super - end - end - end -end diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb deleted file mode 100644 index f5b145230d..0000000000 --- a/activerecord/lib/active_record/type/decimal.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveRecord - module Type - class Decimal < Value # :nodoc: - include Helpers::Numeric - - def type - :decimal - end - - def type_cast_for_schema(value) - value.to_s.inspect - end - - private - - def cast_value(value) - casted_value = case value - when ::Float - convert_float_to_big_decimal(value) - when ::Numeric, ::String - BigDecimal(value, precision.to_i) - else - if value.respond_to?(:to_d) - value.to_d - else - cast_value(value.to_s) - end - end - - scale ? casted_value.round(scale) : casted_value - end - - def convert_float_to_big_decimal(value) - if precision - BigDecimal(value, float_precision) - else - value.to_d - end - end - - def float_precision - if precision.to_i > ::Float::DIG + 1 - ::Float::DIG + 1 - else - precision.to_i - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb deleted file mode 100644 index ff5559e300..0000000000 --- a/activerecord/lib/active_record/type/decimal_without_scale.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_record/type/big_integer' - -module ActiveRecord - module Type - class DecimalWithoutScale < BigInteger # :nodoc: - def type - :decimal - end - end - end -end diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb deleted file mode 100644 index d88482b85d..0000000000 --- a/activerecord/lib/active_record/type/float.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveRecord - module Type - class Float < Value # :nodoc: - include Helpers::Numeric - - def type - :float - end - - alias serialize cast - - private - - def cast_value(value) - case value - when ::Float then value - when "Infinity" then ::Float::INFINITY - when "-Infinity" then -::Float::INFINITY - when "NaN" then ::Float::NAN - else value.to_f - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb deleted file mode 100644 index 3b01e3f8ca..0000000000 --- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveRecord - module Type - class HashLookupTypeMap < TypeMap # :nodoc: - def alias_type(type, alias_type) - register_type(type) { |_, *args| lookup(alias_type, *args) } - end - - def key?(key) - @mapping.key?(key) - end - - def keys - @mapping.keys - end - - private - - def perform_fetch(type, *args, &block) - @mapping.fetch(type, block).call(type, *args) - end - end - end -end diff --git a/activerecord/lib/active_record/type/helpers.rb b/activerecord/lib/active_record/type/helpers.rb deleted file mode 100644 index 634d417d13..0000000000 --- a/activerecord/lib/active_record/type/helpers.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_record/type/helpers/accepts_multiparameter_time' -require 'active_record/type/helpers/numeric' -require 'active_record/type/helpers/mutable' -require 'active_record/type/helpers/time_value' diff --git a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb deleted file mode 100644 index be571fc1c7..0000000000 --- a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActiveRecord - module Type - module Helpers - class AcceptsMultiparameterTime < Module # :nodoc: - def initialize(defaults: {}) - define_method(:cast) do |value| - if value.is_a?(Hash) - value_from_multiparameter_assignment(value) - else - super(value) - end - end - - define_method(:value_from_multiparameter_assignment) do |values_hash| - defaults.each do |k, v| - values_hash[k] ||= v - end - return unless values_hash[1] && values_hash[2] && values_hash[3] - values = values_hash.sort.map(&:last) - ::Time.send( - ActiveRecord::Base.default_timezone, - *values - ) - end - private :value_from_multiparameter_assignment - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activerecord/lib/active_record/type/helpers/mutable.rb deleted file mode 100644 index 88a9099277..0000000000 --- a/activerecord/lib/active_record/type/helpers/mutable.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActiveRecord - module Type - module Helpers - module Mutable # :nodoc: - def cast(value) - deserialize(serialize(value)) - end - - # +raw_old_value+ will be the `_before_type_cast` version of the - # value (likely a string). +new_value+ will be the current, type - # cast value. - def changed_in_place?(raw_old_value, new_value) - raw_old_value != serialize(new_value) - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activerecord/lib/active_record/type/helpers/numeric.rb deleted file mode 100644 index a755a02a59..0000000000 --- a/activerecord/lib/active_record/type/helpers/numeric.rb +++ /dev/null @@ -1,34 +0,0 @@ -module ActiveRecord - module Type - module Helpers - module Numeric # :nodoc: - def 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, new_value_before_type_cast) # :nodoc: - super || number_to_non_number?(old_value, new_value_before_type_cast) - end - - private - - def number_to_non_number?(old_value, new_value_before_type_cast) - old_value != nil && non_numeric_string?(new_value_before_type_cast) - end - - 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.to_s !~ /\A-?\d+\.?\d*\z/ - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/helpers/time_value.rb b/activerecord/lib/active_record/type/helpers/time_value.rb deleted file mode 100644 index 7eb41557cb..0000000000 --- a/activerecord/lib/active_record/type/helpers/time_value.rb +++ /dev/null @@ -1,58 +0,0 @@ -module ActiveRecord - module Type - module Helpers - module TimeValue # :nodoc: - def serialize(value) - if precision && value.respond_to?(:usec) - number_of_insignificant_digits = 6 - precision - round_power = 10 ** number_of_insignificant_digits - value = value.change(usec: value.usec / round_power * round_power) - end - - if value.acts_like?(:time) - zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal - - if value.respond_to?(zone_conversion_method) - value = value.send(zone_conversion_method) - end - end - - value - end - - def type_cast_for_schema(value) - "'#{value.to_s(:db)}'" - end - - def user_input_in_time_zone(value) - value.in_time_zone - end - - private - - def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) - # Treat 0000-00-00 00:00:00 as nil. - return if year.nil? || (year == 0 && mon == 0 && mday == 0) - - if offset - time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil - return unless time - - time -= offset - Base.default_timezone == :utc ? time : time.getlocal - else - ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil - end - end - - # Doesn't handle time zones. - def fast_string_to_time(string) - if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME - microsec = ($7.to_r * 1_000_000).to_i - new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec - end - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb deleted file mode 100644 index c5040c6d3b..0000000000 --- a/activerecord/lib/active_record/type/integer.rb +++ /dev/null @@ -1,66 +0,0 @@ -module ActiveRecord - module Type - class Integer < Value # :nodoc: - include Helpers::Numeric - - # Column storage size in bytes. - # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc. - DEFAULT_LIMIT = 4 - - def initialize(*) - super - @range = min_value...max_value - end - - def type - :integer - end - - def deserialize(value) - return if value.nil? - value.to_i - end - - def serialize(value) - result = cast(value) - if result - ensure_in_range(result) - end - result - end - - protected - - attr_reader :range - - private - - def cast_value(value) - case value - when true then 1 - when false then 0 - else - value.to_i rescue nil - end - end - - def ensure_in_range(value) - unless range.cover?(value) - raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}" - end - end - - def max_value - 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign - end - - def min_value - -max_value - end - - def _limit - self.limit || DEFAULT_LIMIT - end - end - end -end diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb index 963a8245d0..097d1bd363 100644 --- a/activerecord/lib/active_record/type/internal/abstract_json.rb +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -1,8 +1,8 @@ module ActiveRecord module Type module Internal # :nodoc: - class AbstractJson < Type::Value # :nodoc: - include Type::Helpers::Mutable + class AbstractJson < ActiveModel::Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable def type :json diff --git a/activerecord/lib/active_record/type/internal/timezone.rb b/activerecord/lib/active_record/type/internal/timezone.rb new file mode 100644 index 0000000000..07dbb0b2c6 --- /dev/null +++ b/activerecord/lib/active_record/type/internal/timezone.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module Type + module Internal + module Timezone + def is_utc? + ActiveRecord::Base.default_timezone == :utc + end + + def default_timezone + ActiveRecord::Base.default_timezone + end + end + end + end +end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index ea3e0d6a45..203a395415 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -1,7 +1,7 @@ module ActiveRecord module Type - class Serialized < DelegateClass(Type::Value) # :nodoc: - include Helpers::Mutable + class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc: + include ActiveModel::Type::Helpers::Mutable attr_reader :subtype, :coder diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb deleted file mode 100644 index 2662b7e874..0000000000 --- a/activerecord/lib/active_record/type/string.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveRecord - module Type - class String < Value # :nodoc: - def type - :string - end - - def changed_in_place?(raw_old_value, new_value) - if new_value.is_a?(::String) - raw_old_value != new_value - end - end - - def serialize(value) - case value - when ::Numeric, ActiveSupport::Duration then value.to_s - when ::String then ::String.new(value) - when true then "t" - when false then "f" - else super - end - end - - private - - def cast_value(value) - case value - when true then "t" - when false then "f" - # String.new is slightly faster than dup - else ::String.new(value.to_s) - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/text.rb b/activerecord/lib/active_record/type/text.rb deleted file mode 100644 index 26f980f060..0000000000 --- a/activerecord/lib/active_record/type/text.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_record/type/string' - -module ActiveRecord - module Type - class Text < String # :nodoc: - def type - :text - end - end - end -end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb deleted file mode 100644 index 19a10021bc..0000000000 --- a/activerecord/lib/active_record/type/time.rb +++ /dev/null @@ -1,42 +0,0 @@ -module ActiveRecord - module Type - class Time < Value # :nodoc: - include Helpers::TimeValue - include Helpers::AcceptsMultiparameterTime.new( - defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } - ) - - def type - :time - end - - def user_input_in_time_zone(value) - return unless value.present? - - case value - when ::String - value = "2000-01-01 #{value}" - when ::Time - value = value.change(year: 2000, day: 1, month: 1) - end - - super(value) - end - - private - - def cast_value(value) - return value unless value.is_a?(::String) - return if value.empty? - - dummy_time_value = "2000-01-01 #{value}" - - fast_string_to_time(dummy_time_value) || begin - time_hash = ::Date._parse(dummy_time_value) - return if time_hash[:hour].nil? - new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb deleted file mode 100644 index 8ce36cc9af..0000000000 --- a/activerecord/lib/active_record/type/type_map.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'concurrent' - -module ActiveRecord - module Type - class TypeMap # :nodoc: - def initialize - @mapping = {} - @cache = Concurrent::Map.new do |h, key| - h.fetch_or_store(key, Concurrent::Map.new) - end - end - - def lookup(lookup_key, *args) - fetch(lookup_key, *args) { default_value } - end - - def fetch(lookup_key, *args, &block) - @cache[lookup_key].fetch_or_store(args) do - perform_fetch(lookup_key, *args, &block) - end - end - - def register_type(key, value = nil, &block) - raise ::ArgumentError unless value || block - @cache.clear - - if block - @mapping[key] = block - else - @mapping[key] = proc { value } - end - end - - def alias_type(key, target_key) - register_type(key) do |sql_type, *args| - metadata = sql_type[/\(.*\)/, 0] - lookup("#{target_key}#{metadata}", *args) - end - end - - def clear - @mapping.clear - end - - private - - def perform_fetch(lookup_key, *args) - matching_pair = @mapping.reverse_each.detect do |key, _| - key === lookup_key - end - - if matching_pair - matching_pair.last.call(lookup_key, *args) - else - yield lookup_key, *args - end - end - - def default_value - @default_value ||= Value.new - end - end - end -end diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb deleted file mode 100644 index ed3e527483..0000000000 --- a/activerecord/lib/active_record/type/unsigned_integer.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveRecord - module Type - class UnsignedInteger < Integer # :nodoc: - private - - def max_value - super * 2 - end - - def min_value - 0 - end - end - end -end diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb deleted file mode 100644 index 6b9d147ecc..0000000000 --- a/activerecord/lib/active_record/type/value.rb +++ /dev/null @@ -1,104 +0,0 @@ -module ActiveRecord - module Type - class Value - attr_reader :precision, :scale, :limit - - def initialize(precision: nil, limit: nil, scale: nil) - @precision = precision - @scale = scale - @limit = limit - end - - def type # :nodoc: - end - - # Converts a value from database input to the appropriate ruby type. The - # return value of this method will be returned from - # ActiveRecord::AttributeMethods::Read#read_attribute. The default - # implementation just calls Value#cast. - # - # +value+ The raw input, as provided from the database. - def deserialize(value) - cast(value) - end - - # Type casts a value from user input (e.g. from a setter). This value may - # be a string from the form builder, or a ruby object passed to a setter. - # There is currently no way to differentiate between which source it came - # from. - # - # The return value of this method will be returned from - # ActiveRecord::AttributeMethods::Read#read_attribute. See also: - # Value#cast_value. - # - # +value+ The raw input, as provided to the attribute setter. - def cast(value) - cast_value(value) unless value.nil? - end - - # Casts a value from the ruby type to a type that the database knows how - # to understand. The returned value from this method should be a - # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or - # +nil+. - def serialize(value) - value - end - - # Type casts a value for schema dumping. This method is private, as we are - # hoping to remove it entirely. - def type_cast_for_schema(value) # :nodoc: - value.inspect - end - - # These predicates are not documented, as I need to look further into - # their use, and see if they can be removed entirely. - def binary? # :nodoc: - false - end - - # Determines whether a value has changed for dirty checking. +old_value+ - # and +new_value+ will always be type-cast. Types should not need to - # override this method. - def changed?(old_value, new_value, _new_value_before_type_cast) - old_value != new_value - end - - # Determines whether the mutable value has been modified since it was - # read. Returns +false+ by default. If your type returns an object - # which could be mutated, you should override this method. You will need - # to either: - # - # - pass +new_value+ to Value#serialize and compare it to - # +raw_old_value+ - # - # or - # - # - pass +raw_old_value+ to Value#deserialize and compare it to - # +new_value+ - # - # +raw_old_value+ The original value, before being passed to - # +deserialize+. - # - # +new_value+ The current value, after type casting. - def changed_in_place?(raw_old_value, new_value) - false - end - - def ==(other) - self.class == other.class && - precision == other.precision && - scale == other.scale && - limit == other.limit - end - - private - - # Convenience method for types which do not need separate type casting - # behavior for user and database inputs. Called by Value#cast for - # values except +nil+. - def cast_value(value) # :doc: - value - end - end - end -end -- cgit v1.2.3 From e467deb6c66d9b45a0c596706284c140f8e1f1b3 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 21 Sep 2015 09:01:34 -0600 Subject: `TypeMap` and `HashLookupTypeMap` shouldn't be in Active Model These are used by the connection adapters to convert SQL type information into the appropriate type object, and makes no sense outside of the context of Active Record --- activerecord/lib/active_record/type.rb | 7 +-- .../lib/active_record/type/hash_lookup_type_map.rb | 23 ++++++++ activerecord/lib/active_record/type/type_map.rb | 64 ++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 activerecord/lib/active_record/type/hash_lookup_type_map.rb create mode 100644 activerecord/lib/active_record/type/type_map.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 165043021b..5b089d66a0 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -16,14 +16,15 @@ require 'active_model/type/time' require 'active_model/type/unsigned_integer' require 'active_model/type/registry' -require 'active_model/type/type_map' -require 'active_model/type/hash_lookup_type_map' require 'active_record/type/internal/abstract_json' require 'active_record/type/internal/timezone' require 'active_record/type/serialized' require 'active_record/type/adapter_specific_registry' +require 'active_record/type/type_map' +require 'active_record/type/hash_lookup_type_map' + module ActiveRecord module Type @registry = AdapterSpecificRegistry.new @@ -78,8 +79,6 @@ module ActiveRecord Text = ActiveModel::Type::Text UnsignedInteger = ActiveModel::Type::UnsignedInteger Value = ActiveModel::Type::Value - TypeMap = ActiveModel::Type::TypeMap - HashLookupTypeMap = ActiveModel::Type::HashLookupTypeMap register(:big_integer, Type::BigInteger, override: false) register(:binary, Type::Binary, override: false) diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 0000000000..3b01e3f8ca --- /dev/null +++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,23 @@ +module ActiveRecord + module Type + class HashLookupTypeMap < TypeMap # :nodoc: + def alias_type(type, alias_type) + register_type(type) { |_, *args| lookup(alias_type, *args) } + end + + def key?(key) + @mapping.key?(key) + end + + def keys + @mapping.keys + end + + private + + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end + end + end +end diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb new file mode 100644 index 0000000000..8ce36cc9af --- /dev/null +++ b/activerecord/lib/active_record/type/type_map.rb @@ -0,0 +1,64 @@ +require 'concurrent' + +module ActiveRecord + module Type + class TypeMap # :nodoc: + def initialize + @mapping = {} + @cache = Concurrent::Map.new do |h, key| + h.fetch_or_store(key, Concurrent::Map.new) + end + end + + def lookup(lookup_key, *args) + fetch(lookup_key, *args) { default_value } + end + + def fetch(lookup_key, *args, &block) + @cache[lookup_key].fetch_or_store(args) do + perform_fetch(lookup_key, *args, &block) + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + @cache.clear + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type, *args| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}", *args) + end + end + + def clear + @mapping.clear + end + + private + + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end + end + + def default_value + @default_value ||= Value.new + end + end + end +end -- cgit v1.2.3 From 22cc2b86f790b08bf58311bb6eb3bb148cb1dea9 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 21 Sep 2015 09:24:39 -0600 Subject: Various stylistic nitpicks We do not need to require each file from AM individually, the type module does that for us. Even if the classes are extremely small right now, I'd rather keep any custom classes needed by AR in their own files, as they can easily have more complex changes in the future. --- activerecord/lib/active_record/type.rb | 56 +++++++--------------- activerecord/lib/active_record/type/date.rb | 7 +++ activerecord/lib/active_record/type/date_time.rb | 7 +++ .../lib/active_record/type/internal/timezone.rb | 2 +- activerecord/lib/active_record/type/time.rb | 8 ++++ activerecord/lib/active_record/type/type_map.rb | 2 +- 6 files changed, 40 insertions(+), 42 deletions(-) create mode 100644 activerecord/lib/active_record/type/date.rb create mode 100644 activerecord/lib/active_record/type/date_time.rb create mode 100644 activerecord/lib/active_record/type/time.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 5b089d66a0..28ab07e868 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -1,24 +1,12 @@ -require 'active_model/type/helpers' -require 'active_model/type/value' - -require 'active_model/type/big_integer' -require 'active_model/type/binary' -require 'active_model/type/boolean' -require 'active_model/type/date' -require 'active_model/type/date_time' -require 'active_model/type/decimal' -require 'active_model/type/decimal_without_scale' -require 'active_model/type/float' -require 'active_model/type/integer' -require 'active_model/type/string' -require 'active_model/type/text' -require 'active_model/type/time' -require 'active_model/type/unsigned_integer' - -require 'active_model/type/registry' +require 'active_model/type' require 'active_record/type/internal/abstract_json' require 'active_record/type/internal/timezone' + +require 'active_record/type/date' +require 'active_record/type/date_time' +require 'active_record/type/time' + require 'active_record/type/serialized' require 'active_record/type/adapter_specific_registry' @@ -56,29 +44,17 @@ module ActiveRecord end end - class Date < ActiveModel::Type::Date - include Internal::Timezone - end - - class DateTime < ActiveModel::Type::DateTime - include Internal::Timezone - end - class Time < ActiveModel::Type::Time - include Internal::Timezone - end - - Helpers = ActiveModel::Type::Helpers - BigInteger = ActiveModel::Type::BigInteger - Binary = ActiveModel::Type::Binary - Boolean = ActiveModel::Type::Boolean - Decimal = ActiveModel::Type::Decimal + BigInteger = ActiveModel::Type::BigInteger + Binary = ActiveModel::Type::Binary + Boolean = ActiveModel::Type::Boolean + Decimal = ActiveModel::Type::Decimal DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale - Float = ActiveModel::Type::Float - Integer = ActiveModel::Type::Integer - String = ActiveModel::Type::String - Text = ActiveModel::Type::Text - UnsignedInteger = ActiveModel::Type::UnsignedInteger - Value = ActiveModel::Type::Value + Float = ActiveModel::Type::Float + Integer = ActiveModel::Type::Integer + String = ActiveModel::Type::String + Text = ActiveModel::Type::Text + UnsignedInteger = ActiveModel::Type::UnsignedInteger + Value = ActiveModel::Type::Value register(:big_integer, Type::BigInteger, override: false) register(:binary, Type::Binary, override: false) diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb new file mode 100644 index 0000000000..ccafed054e --- /dev/null +++ b/activerecord/lib/active_record/type/date.rb @@ -0,0 +1,7 @@ +module ActiveRecord + module Type + class Date < ActiveModel::Type::Date + include Internal::Timezone + end + end +end diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb new file mode 100644 index 0000000000..1fb9380ecd --- /dev/null +++ b/activerecord/lib/active_record/type/date_time.rb @@ -0,0 +1,7 @@ +module ActiveRecord + module Type + class DateTime < ActiveModel::Type::DateTime + include Internal::Timezone + end + end +end diff --git a/activerecord/lib/active_record/type/internal/timezone.rb b/activerecord/lib/active_record/type/internal/timezone.rb index 07dbb0b2c6..947e06158a 100644 --- a/activerecord/lib/active_record/type/internal/timezone.rb +++ b/activerecord/lib/active_record/type/internal/timezone.rb @@ -5,7 +5,7 @@ module ActiveRecord def is_utc? ActiveRecord::Base.default_timezone == :utc end - + def default_timezone ActiveRecord::Base.default_timezone end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb new file mode 100644 index 0000000000..70988d84ff --- /dev/null +++ b/activerecord/lib/active_record/type/time.rb @@ -0,0 +1,8 @@ +module ActiveRecord + module Type + class Time < ActiveModel::Type::Time + include Internal::Timezone + end + end +end + diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 8ce36cc9af..81d7ed39bb 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -57,7 +57,7 @@ module ActiveRecord end def default_value - @default_value ||= Value.new + @default_value ||= ActiveModel::Type::Value.new end end end -- cgit v1.2.3 From 4590d7729e241cb7f66e018a2a9759cb3baa36e5 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 21 Sep 2015 09:40:04 -0600 Subject: Simplify the implementation of Active Model's type registry Things like decorations, overrides, and priorities only matter for Active Record, so the Active Model registry can be implemented much more simply. At this point, I wonder if having Active Record's registry inherit from Active Model's is even worth the trouble? The Active Model class was also missing test cases, which have been backfilled. This removes the error when two types are registered with the same name, but given that Active Model is meant to be significantly more generic, I do not think this is an issue for now. If we want, we can raise an error at the point that someone tries to register it. --- .../lib/active_record/type/adapter_specific_registry.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb index 3509429058..d440eac619 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -4,14 +4,20 @@ module ActiveRecord # :stopdoc: module Type class AdapterSpecificRegistry < ActiveModel::Type::Registry + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + private - + def registration_klass Registration end - - def decoration_registration_klass - DecorationRegistration + + def find_registration(symbol, *args) + registrations + .select { |registration| registration.matches?(symbol, *args) } + .max end end @@ -118,8 +124,7 @@ module ActiveRecord end end - class TypeConflictError < ::ActiveModel::TypeConflictError + class TypeConflictError < StandardError end - # :startdoc: end -- cgit v1.2.3