1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

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
This commit is contained in:
Sean Griffin 2016-01-08 14:09:31 -07:00
parent c1a1595740
commit 302e92359c
5 changed files with 67 additions and 8 deletions

View file

@ -84,6 +84,10 @@ module ActiveModel
false
end
def map(value) # :nodoc:
yield value
end
def ==(other)
self.class == other.class &&
precision == other.precision &&

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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'))