aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_decorators.rb
diff options
context:
space:
mode:
authorSean Griffin <sean@thoughtbot.com>2014-06-16 14:55:01 -0600
committerSean Griffin <sean@thoughtbot.com>2014-06-16 15:09:39 -0600
commit74af9f7fd781501e7f4a4746afd91b1bd5f77ddc (patch)
tree47c95a656a5b0aa0a8a6c72c1282832e13122004 /activerecord/lib/active_record/attribute_decorators.rb
parent88714deb677f598fa40f6e7b61a083a5461d07fd (diff)
downloadrails-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.rb36
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