diff options
Diffstat (limited to 'activerecord/lib/active_record/type')
11 files changed, 171 insertions, 37 deletions
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb new file mode 100644 index 0000000000..5f71b3cb94 --- /dev/null +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,142 @@ +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 + end + + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + + protected + + attr_reader :registrations + end + + class Registration + def initialize(name, block, adapter: nil, override: nil) + @name = name + @block = block + @adapter = adapter + @override = override + end + + def call(_registry, *args, adapter: nil, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + end + + def matches?(type_name, *args, **kwargs) + type_name == name && matches_adapter?(**kwargs) + end + + def <=>(other) + if conflicts_with?(other) + raise TypeConflictError.new("Type #{name} was registered for all + adapters, but shadows a native type with + the same name for #{other.adapter}".squish) + end + priority <=> other.priority + end + + protected + + attr_reader :name, :block, :adapter, :override + + def priority + result = 0 + if adapter + result |= 1 + end + if override + result |= 2 + end + result + end + + def priority_except_adapter + priority & 0b111111100 + end + + private + + def matches_adapter?(adapter: nil, **) + (self.adapter.nil? || adapter == self.adapter) + end + + def conflicts_with?(other) + same_priority_except_adapter?(other) && + has_adapter_conflict?(other) + end + + def same_priority_except_adapter?(other) + priority_except_adapter == other.priority_except_adapter + end + + def has_adapter_conflict?(other) + (override.nil? && other.adapter) || + (adapter && other.override.nil?) + end + end + + class DecorationRegistration < Registration + def initialize(options, klass, adapter: nil) + @options = options + @klass = klass + @adapter = adapter + end + + def call(registry, *args, **kwargs) + subtype = registry.lookup(*args, **kwargs.except(*options.keys)) + klass.new(subtype) + end + + def matches?(*args, **kwargs) + matches_adapter?(**kwargs) && matches_options?(**kwargs) + end + + def priority + super | 4 + end + + protected + + attr_reader :options, :klass + + private + + def matches_options?(**kwargs) + options.all? do |key, value| + kwargs[key] == value + end + end + end + end + + class TypeConflictError < StandardError + end + + # :startdoc: +end diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb index 005a48ef0d..0baf8c63ad 100644 --- a/activerecord/lib/active_record/type/binary.rb +++ b/activerecord/lib/active_record/type/binary.rb @@ -9,7 +9,7 @@ module ActiveRecord true end - def type_cast(value) + def cast(value) if value.is_a?(Data) value.to_s else @@ -17,13 +17,13 @@ module ActiveRecord end end - def type_cast_for_database(value) + def serialize(value) return if value.nil? Data.new(super) end def changed_in_place?(raw_old_value, value) - old_value = type_cast_from_database(raw_old_value) + old_value = deserialize(raw_old_value) old_value != value end diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb index a25f2521bb..05d2af3808 100644 --- a/activerecord/lib/active_record/type/date_time.rb +++ b/activerecord/lib/active_record/type/date_time.rb @@ -10,7 +10,7 @@ module ActiveRecord :datetime end - def type_cast_for_database(value) + def serialize(value) if precision && value.respond_to?(:usec) number_of_insignificant_digits = 6 - precision round_power = 10 ** number_of_insignificant_digits diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb index b3928242b7..d88482b85d 100644 --- a/activerecord/lib/active_record/type/float.rb +++ b/activerecord/lib/active_record/type/float.rb @@ -7,7 +7,7 @@ module ActiveRecord :float end - alias type_cast_for_database type_cast + alias serialize cast private diff --git a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb index 640943c5e9..be571fc1c7 100644 --- a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb +++ b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb @@ -3,7 +3,7 @@ module ActiveRecord module Helpers class AcceptsMultiparameterTime < Module # :nodoc: def initialize(defaults: {}) - define_method(:type_cast_from_user) do |value| + define_method(:cast) do |value| if value.is_a?(Hash) value_from_multiparameter_assignment(value) else diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activerecord/lib/active_record/type/helpers/mutable.rb index dc37f4a885..88a9099277 100644 --- a/activerecord/lib/active_record/type/helpers/mutable.rb +++ b/activerecord/lib/active_record/type/helpers/mutable.rb @@ -2,15 +2,15 @@ module ActiveRecord module Type module Helpers module Mutable # :nodoc: - def type_cast_from_user(value) - type_cast_from_database(type_cast_for_database(value)) + 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 != type_cast_for_database(new_value) + raw_old_value != serialize(new_value) end end end diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activerecord/lib/active_record/type/helpers/numeric.rb index b0d4f03117..a755a02a59 100644 --- a/activerecord/lib/active_record/type/helpers/numeric.rb +++ b/activerecord/lib/active_record/type/helpers/numeric.rb @@ -2,7 +2,7 @@ module ActiveRecord module Type module Helpers module Numeric # :nodoc: - def type_cast(value) + def cast(value) value = case value when true then 1 when false then 0 diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index 2ab2402dfd..2a1b04ac7f 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -16,13 +16,13 @@ module ActiveRecord :integer end - def type_cast_from_database(value) + def deserialize(value) return if value.nil? value.to_i end - def type_cast_for_database(value) - result = type_cast(value) + def serialize(value) + result = cast(value) if result ensure_in_range(result) end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 6c6c520048..732029c723 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -11,7 +11,7 @@ module ActiveRecord super(subtype) end - def type_cast_from_database(value) + def deserialize(value) if default_value?(value) value else @@ -19,7 +19,7 @@ module ActiveRecord end end - def type_cast_for_database(value) + def serialize(value) return if value.nil? unless default_value?(value) super coder.dump(value) @@ -28,7 +28,7 @@ module ActiveRecord def changed_in_place?(raw_old_value, value) return false if value.nil? - subtype.changed_in_place?(raw_old_value, type_cast_for_database(value)) + subtype.changed_in_place?(raw_old_value, serialize(value)) end def accessor diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb index fbc0af2c5a..2662b7e874 100644 --- a/activerecord/lib/active_record/type/string.rb +++ b/activerecord/lib/active_record/type/string.rb @@ -11,7 +11,7 @@ module ActiveRecord end end - def type_cast_for_database(value) + def serialize(value) case value when ::Numeric, ActiveSupport::Duration then value.to_s when ::String then ::String.new(value) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 7338920f3b..fc3ef5e83b 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -14,12 +14,12 @@ module ActiveRecord # Convert 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. See also - # Value#type_cast and Value#cast_value. + # ActiveRecord::AttributeMethods::Read#read_attribute. The default + # implementation just calls Value#cast. # # +value+ The raw input, as provided from the database. - def type_cast_from_database(value) - type_cast(value) + def deserialize(value) + cast(value) end # Type casts a value from user input (e.g. from a setter). This value may @@ -29,18 +29,18 @@ module ActiveRecord # # The return value of this method will be returned from # ActiveRecord::AttributeMethods::Read#read_attribute. See also: - # Value#type_cast and Value#cast_value. + # Value#cast_value. # # +value+ The raw input, as provided to the attribute setter. - def type_cast_from_user(value) - type_cast(value) + def cast(value) + cast_value(value) unless value.nil? end # Cast 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 type_cast_for_database(value) + def serialize(value) value end @@ -68,16 +68,16 @@ module ActiveRecord # which could be mutated, you should override this method. You will need # to either: # - # - pass +new_value+ to Value#type_cast_for_database and compare it to + # - pass +new_value+ to Value#serialize and compare it to # +raw_old_value+ # # or # - # - pass +raw_old_value+ to Value#type_cast_from_database and compare it to + # - pass +raw_old_value+ to Value#deserialize and compare it to # +new_value+ # # +raw_old_value+ The original value, before being passed to - # +type_cast_from_database+. + # +deserialize+. # # +new_value+ The current value, after type casting. def changed_in_place?(raw_old_value, new_value) @@ -93,16 +93,8 @@ module ActiveRecord private - # Convenience method. If you don't need separate behavior for - # Value#type_cast_from_database and Value#type_cast_from_user, you can override - # this method instead. The default behavior of both methods is to call - # this one. See also Value#cast_value. - def type_cast(value) # :doc: - cast_value(value) unless value.nil? - end - # Convenience method for types which do not need separate type casting - # behavior for user and database inputs. Called by Value#type_cast for + # behavior for user and database inputs. Called by Value#cast for # values except +nil+. def cast_value(value) # :doc: value |