diff options
author | Sean Griffin <sean@thoughtbot.com> | 2014-06-16 14:55:01 -0600 |
---|---|---|
committer | Sean Griffin <sean@thoughtbot.com> | 2014-06-16 15:09:39 -0600 |
commit | 74af9f7fd781501e7f4a4746afd91b1bd5f77ddc (patch) | |
tree | 47c95a656a5b0aa0a8a6c72c1282832e13122004 /activerecord/lib/active_record/attribute_decorators.rb | |
parent | 88714deb677f598fa40f6e7b61a083a5461d07fd (diff) | |
download | rails-74af9f7fd781501e7f4a4746afd91b1bd5f77ddc.tar.gz rails-74af9f7fd781501e7f4a4746afd91b1bd5f77ddc.tar.bz2 rails-74af9f7fd781501e7f4a4746afd91b1bd5f77ddc.zip |
Promote time zone aware attributes to a first class type decorator
This refactoring revealed the need for another form of decoration, which
takes a proc to select which it applies to (There's a *lot* of cases
where this form can be used). To avoid duplication, we can re-implement
the old decoration in terms of the proc-based decoration.
The reason we're `instance_exec`ing the matcher is for cases such as
time zone aware attributes, where a decorator is defined in a parent
class, and a method called in the matcher is overridden by a child
class. The matcher will close over the parent, and evaluate in its
context, which is not the behavior we want.
Diffstat (limited to 'activerecord/lib/active_record/attribute_decorators.rb')
-rw-r--r-- | activerecord/lib/active_record/attribute_decorators.rb | 36 |
1 files changed, 26 insertions, 10 deletions
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 928d8cacae..92627f8d5d 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -9,18 +9,24 @@ module ActiveRecord module ClassMethods def decorate_attribute_type(column_name, decorator_name, &block) + matcher = ->(name, _) { name == column_name.to_s } + key = "_#{column_name}_#{decorator_name}" + decorate_matching_attribute_types(matcher, key, &block) + end + + def decorate_matching_attribute_types(matcher, decorator_name, &block) clear_caches_calculated_from_columns - column_name = column_name.to_s + decorator_name = decorator_name.to_s # Create new hashes so we don't modify parent classes - self.attribute_type_decorations = attribute_type_decorations.merge(column_name, decorator_name, block) + self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block]) end private def add_user_provided_columns(*) super.map do |column| - decorated_type = attribute_type_decorations.apply(column.name, column.cast_type) + decorated_type = attribute_type_decorations.apply(self, column.name, column.cast_type) column.with_type(decorated_type) end end @@ -29,22 +35,32 @@ module ActiveRecord class TypeDecorator delegate :clear, to: :@decorations - def initialize(decorations = Hash.new({})) + def initialize(decorations = {}) @decorations = decorations end - def merge(attribute_name, decorator_name, block) - decorations_for_attribute = @decorations[attribute_name] - new_decorations = decorations_for_attribute.merge(decorator_name.to_s => block) - TypeDecorator.new(@decorations.merge(attribute_name => new_decorations)) + def merge(*args) + TypeDecorator.new(@decorations.merge(*args)) end - def apply(attribute_name, type) - decorations = @decorations[attribute_name].values + def apply(context, name, type) + decorations = decorators_for(context, name, type) decorations.inject(type) do |new_type, block| block.call(new_type) end end + + private + + def decorators_for(context, name, type) + matching(context, name, type).map(&:last) + end + + def matching(context, name, type) + @decorations.values.select do |(matcher, _)| + context.instance_exec(name, type, &matcher) + end + end end end end |