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:
parent
c1a1595740
commit
302e92359c
5 changed files with 67 additions and 8 deletions
|
@ -84,6 +84,10 @@ module ActiveModel
|
|||
false
|
||||
end
|
||||
|
||||
def map(value) # :nodoc:
|
||||
yield value
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
precision == other.precision &&
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'))
|
||||
|
|
Loading…
Reference in a new issue