aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Griffin <sean@seantheprogrammer.com>2016-01-08 14:09:31 -0700
committerSean Griffin <sean@seantheprogrammer.com>2016-01-08 14:11:45 -0700
commit302e92359cc88258ae15a82454c58408a4b8157e (patch)
treebec6f312290020841080ccca0878fd2f2bf57fe5
parentc1a1595740b243bed02f5e59090cc58dac77bbf3 (diff)
downloadrails-302e92359cc88258ae15a82454c58408a4b8157e.tar.gz
rails-302e92359cc88258ae15a82454c58408a4b8157e.tar.bz2
rails-302e92359cc88258ae15a82454c58408a4b8157e.zip
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
-rw-r--r--activemodel/lib/active_model/type/value.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb42
5 files changed, 67 insertions, 8 deletions
diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 9d1f267b41..0d2d6873a8 100644
--- a/activemodel/lib/active_model/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -84,6 +84,10 @@ module ActiveModel
false
end
+ def map(value) # :nodoc:
+ yield value
+ end
+
def ==(other)
self.class == other.class &&
precision == other.precision &&
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)
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 02b1083430..0edfa4ed9d 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -4,11 +4,13 @@ require 'support/connection_helper'
if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
self.table_name = "postgresql_ranges"
+ self.time_zone_aware_types += [:tsrange, :tstzrange]
end
class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
include ConnectionHelper
+ include InTimeZone
def setup
@connection = PostgresqlRange.connection
@@ -160,6 +162,26 @@ _SQL
assert_nil @empty_range.float_range
end
+ def test_timezone_awareness_tzrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(tstz_range: time_string..time_string)
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.tstz_range
+ assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
+ end
+ end
+
def test_create_tstzrange
tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT')
round_trip(@new_range, :tstz_range, tstzrange)
@@ -188,6 +210,26 @@ _SQL
Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0))
end
+ def test_timezone_awareness_tsrange
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
BigDecimal.new('0.5')...BigDecimal.new('1'))