From 302e92359cc88258ae15a82454c58408a4b8157e Mon Sep 17 00:00:00 2001
From: Sean Griffin <sean@seantheprogrammer.com>
Date: Fri, 8 Jan 2016 14:09:31 -0700
Subject: Refactor tz aware types, add support for PG ranges

This is an alternate implementation to #22875, that generalizes a lot of
the logic that type decorators are going to need, in order to have them
work with arrays, ranges, etc. The types have the ability to map over a
value, with the default implementation being to just yield that given
value. Array and Range give more appropriate definitions.

This does not automatically make ranges time zone aware, as they need to
be added to the `time_zone_aware` types config, but we could certainly
make that change if we feel it is appropriate. I do think this would be
a breaking change however, and should at least have a deprecation cycle.

Closes #22875.

/cc @matthewd
---
 .../attribute_methods/time_zone_conversion.rb            | 16 +++++++++-------
 .../connection_adapters/postgresql/oid/array.rb          |  4 ++++
 .../connection_adapters/postgresql/oid/range.rb          |  9 ++++++++-
 3 files changed, 21 insertions(+), 8 deletions(-)

(limited to 'activerecord/lib')

diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 45d2c855a5..d9b9271fb0 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -9,9 +9,9 @@ module ActiveRecord
         end
 
         def cast(value)
-          if value.is_a?(Array)
-            value.map { |v| cast(v) }
-          elsif value.is_a?(Hash)
+          return if value.nil?
+
+          if value.is_a?(Hash)
             set_time_zone_without_conversion(super)
           elsif value.respond_to?(:in_time_zone)
             begin
@@ -19,18 +19,20 @@ module ActiveRecord
             rescue ArgumentError
               nil
             end
+          else
+            map(super) { |t| cast(t) }
           end
         end
 
         private
 
         def convert_time_to_time_zone(value)
-          if value.is_a?(Array)
-            value.map { |v| convert_time_to_time_zone(v) }
-          elsif value.acts_like?(:time)
+          return if value.nil?
+
+          if value.acts_like?(:time)
             value.in_time_zone
           else
-            value
+            map(value) { |v| convert_time_to_time_zone(v) }
           end
         end
 
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index 25961a9869..87593ef704 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -50,6 +50,10 @@ module ActiveRecord
             "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
           end
 
+          def map(value, &block)
+            value.map(&block)
+          end
+
           private
 
           def type_cast_array(value, method)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index fc201f8fb9..a8d2310035 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -6,6 +6,7 @@ module ActiveRecord
       module OID # :nodoc:
         class Range < Type::Value # :nodoc:
           attr_reader :subtype, :type
+          delegate :user_input_in_time_zone, to: :subtype
 
           def initialize(subtype, type = :range)
             @subtype = subtype
@@ -18,7 +19,7 @@ module ActiveRecord
 
           def cast_value(value)
             return if value == 'empty'
-            return value if value.is_a?(::Range)
+            return value unless value.is_a?(::String)
 
             extracted = extract_bounds(value)
             from = type_cast_single extracted[:from]
@@ -46,6 +47,12 @@ module ActiveRecord
               other.type == type
           end
 
+          def map(value) # :nodoc:
+            new_begin = yield(value.begin)
+            new_end = yield(value.end)
+            ::Range.new(new_begin, new_end, value.exclude_end?)
+          end
+
           private
 
           def type_cast_single(value)
-- 
cgit v1.2.3