提交 302e9235 编写于 作者: S Sean Griffin

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
上级 c1a15957
......@@ -84,6 +84,10 @@ def changed_in_place?(raw_old_value, new_value)
false
end
def map(value) # :nodoc:
yield value
end
def ==(other)
self.class == other.class &&
precision == other.precision &&
......
......@@ -9,9 +9,9 @@ def deserialize(value)
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 @@ def cast(value)
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
......
......@@ -50,6 +50,10 @@ def type_cast_for_schema(value)
"[" + 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)
......
......@@ -6,6 +6,7 @@ module PostgreSQL
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 @@ def type_cast_for_schema(value)
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 @@ def ==(other)
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)
......
......@@ -4,11 +4,13 @@
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 @@ def test_custom_range_values
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 @@ def test_update_tsrange
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'))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册