summaryrefslogtreecommitdiff
path: root/lib/hashie/extensions/coercion.rb
diff options
context:
space:
mode:
authorVladimir Kochnev <hashtable@yandex.ru>2015-09-02 20:16:23 +0300
committerVladimir Kochnev <hashtable@yandex.ru>2015-09-02 20:34:37 +0300
commit15c128e2e37f644b63dd113c040f1223a15d6996 (patch)
tree060042a5578474bd831d85d74a33ff05967af9af /lib/hashie/extensions/coercion.rb
parent11c0ac707f908c19dda72e6e863b06fb069f06b5 (diff)
downloadhashie-15c128e2e37f644b63dd113c040f1223a15d6996.tar.gz
Refactor `Hashie::Extensions::Coercion`, add cache.
Diffstat (limited to 'lib/hashie/extensions/coercion.rb')
-rw-r--r--lib/hashie/extensions/coercion.rb104
1 files changed, 65 insertions, 39 deletions
diff --git a/lib/hashie/extensions/coercion.rb b/lib/hashie/extensions/coercion.rb
index 44eea18..abb0e77 100644
--- a/lib/hashie/extensions/coercion.rb
+++ b/lib/hashie/extensions/coercion.rb
@@ -29,51 +29,17 @@ module Hashie
def set_value_with_coercion(key, value)
into = self.class.key_coercion(key) || self.class.value_coercion(value)
- return set_value_without_coercion(key, value) if value.nil? || into.nil?
-
- begin
- return set_value_without_coercion(key, coerce_or_init(into).call(value)) unless into.is_a?(Enumerable)
-
- if into.class <= ::Hash
- key_coerce = coerce_or_init(into.flatten[0])
- value_coerce = coerce_or_init(into.flatten[-1])
- value = into.class[value.map { |k, v| [key_coerce.call(k), value_coerce.call(v)] }]
- else # Enumerable but not Hash: Array, Set
- value_coerce = coerce_or_init(into.first)
- value = into.class.new(value.map { |v| value_coerce.call(v) })
+ unless value.nil? || into.nil?
+ begin
+ value = self.class.fetch_coercion(into).call(value)
+ rescue NoMethodError, TypeError => e
+ raise CoercionError, "Cannot coerce property #{key.inspect} from #{value.class} to #{into}: #{e.message}"
end
- rescue NoMethodError, TypeError => e
- raise CoercionError, "Cannot coerce property #{key.inspect} from #{value.class} to #{into}: #{e.message}"
end
set_value_without_coercion(key, value)
end
- def coerce_or_init(type)
- return type if type.is_a? Proc
-
- if CORE_TYPES.key?(type)
- lambda do |v|
- return v if v.is_a? type
- return v.send(CORE_TYPES[type])
- end
- elsif type.respond_to?(:coerce)
- lambda do |v|
- return v if v.is_a? type
- type.coerce(v)
- end
- elsif type.respond_to?(:new)
- lambda do |v|
- return v if v.is_a? type
- type.new(v)
- end
- else
- fail TypeError, "#{type} is not a coercable type"
- end
- end
-
- private :coerce_or_init
-
def custom_writer(key, value, _convert = true)
self[key] = value
end
@@ -175,6 +141,66 @@ module Hashie
strict_value_coercions[from] || lenient_value_coercions[from]
end
+ def fetch_coercion(type)
+ return type if type.is_a? Proc
+ coercion_cache[type]
+ end
+
+ def coercion_cache
+ @coercion_cache ||= ::Hash.new do |hash, type|
+ hash[type] = build_coercion(type)
+ end
+ end
+
+ def build_coercion(type)
+ if type.is_a? Enumerable
+ if type.class <= ::Hash
+ type, key_type, value_type = type.class, *type.first
+ build_hash_coercion(type, key_type, value_type)
+ else # Enumerable but not Hash: Array, Set
+ type, value_type = type.class, type.first
+ build_container_coercion(type, value_type)
+ end
+ elsif CORE_TYPES.key? type
+ build_core_type_coercion(type)
+ elsif type.respond_to? :coerce
+ lambda do |value|
+ return value if value.is_a? type
+ type.coerce(value)
+ end
+ elsif type.respond_to? :new
+ lambda do |value|
+ return value if value.is_a? type
+ type.new(value)
+ end
+ else
+ fail TypeError, "#{type} is not a coercable type"
+ end
+ end
+
+ def build_hash_coercion(type, key_type, value_type)
+ key_coerce = fetch_coercion(key_type)
+ value_coerce = fetch_coercion(value_type)
+ lambda do |value|
+ type[value.map { |k, v| [key_coerce.call(k), value_coerce.call(v)] }]
+ end
+ end
+
+ def build_container_coercion(type, value_type)
+ value_coerce = fetch_coercion(value_type)
+ lambda do |value|
+ type.new(value.map { |v| value_coerce.call(v) })
+ end
+ end
+
+ def build_core_type_coercion(type)
+ name = CORE_TYPES[type]
+ lambda do |value|
+ return value if value.is_a? type
+ return value.send(name)
+ end
+ end
+
def inherited(klass)
super