diff options
author | Sean Griffin <sean@thoughtbot.com> | 2015-02-15 13:49:21 -0700 |
---|---|---|
committer | Sean Griffin <sean@thoughtbot.com> | 2015-02-15 14:22:08 -0700 |
commit | 8c837e5fcc2a3ac639b3b93b0024bd2c20d3b6ed (patch) | |
tree | 48c22796cb5e039bc4f21cc75458f4467ecf76eb /activerecord/lib | |
parent | 1747c4e2cea811cbf04fccc9f57256c80112d9ce (diff) | |
download | rails-8c837e5fcc2a3ac639b3b93b0024bd2c20d3b6ed.tar.gz rails-8c837e5fcc2a3ac639b3b93b0024bd2c20d3b6ed.tar.bz2 rails-8c837e5fcc2a3ac639b3b93b0024bd2c20d3b6ed.zip |
Add a global type registry, used to lookup and register types
As per previous discussions, we want to give users the ability to
reference their own types with symbols, instead of having to pass the
object manually. This adds the class that will be used to do so.
ActiveRecord::Type.register(:money, MyMoneyType)
Diffstat (limited to 'activerecord/lib')
-rw-r--r-- | activerecord/lib/active_record/type.rb | 25 | ||||
-rw-r--r-- | activerecord/lib/active_record/type/adapter_specific_registry.rb | 139 |
2 files changed, 164 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index f18b076d58..7ce0370d8b 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -16,5 +16,30 @@ require 'active_record/type/text' require 'active_record/type/time' require 'active_record/type/unsigned_integer' +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 + + class << self + attr_accessor :registry # :nodoc: + + def register(*args) + registry.register(*args) + end + + def lookup(*args, adapter: current_adapter_name, **kwargs) + registry.lookup(*args, adapter: adapter, **kwargs) + end + + private + + def current_adapter_name + ActiveRecord::Base.connection.adapter_name.downcase.to_sym + end + end + end +end 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..ce9ff6a557 --- /dev/null +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,139 @@ +module ActiveRecord + module Type + class AdapterSpecificRegistry # :nodoc: + 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 +end |