Time columns should support time zone aware attributes
The types that are affected by `time_zone_aware_attributes` (which is on by default) have been made configurable, in case this is a breaking change for existing applications.
This commit is contained in:
parent
d8e710410e
commit
5cd3bbbb83
@ -1,3 +1,17 @@
|
||||
* `time` columns can now affected by `time_zone_aware_attributes`. If you have
|
||||
set `config.time_zone` to a value other than `'UTC'`, they will be treated
|
||||
as in that time zone by default in Rails 5.0. If this is not the desired
|
||||
behavior, you can set
|
||||
|
||||
ActiveRecord::Base.time_zone_aware_types = [:datetime]
|
||||
|
||||
A deprecation warning will be emitted if you have a `:time` column, and have
|
||||
not explicitly opted out.
|
||||
|
||||
Fixes #3145
|
||||
|
||||
*Sean Griffin*
|
||||
|
||||
* `nil` as a value for a binary column in a query no longer logs as
|
||||
"<NULL binary data>", and instead logs as just "nil".
|
||||
|
||||
|
@ -13,7 +13,7 @@ def type_cast_from_user(value)
|
||||
value.map { |v| type_cast_from_user(v) }
|
||||
elsif value.respond_to?(:in_time_zone)
|
||||
begin
|
||||
value.in_time_zone || super
|
||||
user_input_in_time_zone(value) || super
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
@ -39,6 +39,9 @@ def convert_time_to_time_zone(value)
|
||||
|
||||
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
|
||||
self.skip_time_zone_conversion_for_attributes = []
|
||||
|
||||
class_attribute :time_zone_aware_types, instance_writer: false
|
||||
self.time_zone_aware_types = [:datetime, :not_explicitly_configured]
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
@ -59,9 +62,31 @@ def inherited(subclass)
|
||||
end
|
||||
|
||||
def create_time_zone_conversion_attribute?(name, cast_type)
|
||||
time_zone_aware_attributes &&
|
||||
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
|
||||
(:datetime == cast_type.type)
|
||||
enabled_for_column = time_zone_aware_attributes &&
|
||||
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
||||
result = enabled_for_column &&
|
||||
time_zone_aware_types.include?(cast_type.type)
|
||||
|
||||
if enabled_for_column &&
|
||||
!result &&
|
||||
cast_type.type == :time &&
|
||||
time_zone_aware_types.include?(:not_explicitly_configured)
|
||||
ActiveSupport::Deprecation.warn(<<-MESSAGE)
|
||||
Time columns will become time zone aware in Rails 5.1. This
|
||||
sill cause `String`s to be parsed as if they were in `Time.zone`,
|
||||
and `Time`s to be converted to `Time.zone`.
|
||||
|
||||
To keep the old behavior, you must add the following to your initializer:
|
||||
|
||||
config.active_record.time_zone_aware_types = [:datetime]
|
||||
|
||||
To silence this deprecation warning, add the following:
|
||||
|
||||
config.active_record.time_zone_aware_types << :time
|
||||
MESSAGE
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ class Array < Type::Value # :nodoc:
|
||||
end
|
||||
|
||||
attr_reader :subtype, :delimiter
|
||||
delegate :type, to: :subtype
|
||||
delegate :type, :user_input_in_time_zone, to: :subtype
|
||||
|
||||
def initialize(subtype, delimiter = ',')
|
||||
@subtype = subtype
|
||||
|
@ -7,6 +7,19 @@ def type
|
||||
:time
|
||||
end
|
||||
|
||||
def user_input_in_time_zone(value)
|
||||
return unless value.present?
|
||||
|
||||
case value
|
||||
when ::String
|
||||
value = "2000-01-01 #{value}"
|
||||
when ::Time
|
||||
value = value.change(year: 2000, day: 1, month: 1)
|
||||
end
|
||||
|
||||
super(value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cast_value(value)
|
||||
|
@ -9,6 +9,10 @@ def type_cast_for_schema(value)
|
||||
"'#{value.to_s(:db)}'"
|
||||
end
|
||||
|
||||
def user_input_in_time_zone(value)
|
||||
value.in_time_zone
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
||||
|
@ -657,7 +657,7 @@ def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_i
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_time_zone_aware_attribute_in_current_time_zone
|
||||
def test_setting_time_zone_aware_datetime_in_current_time_zone
|
||||
utc_time = Time.utc(2008, 1, 1)
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
record = @target.new
|
||||
@ -676,6 +676,47 @@ def test_yaml_dumping_record_with_time_zone_aware_attribute
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_time_zone_aware_time_in_current_time_zone
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
record = @target.new
|
||||
time_string = "10:00:00"
|
||||
expected_time = Time.zone.parse("2000-01-01 #{time_string}")
|
||||
|
||||
record.bonus_time = time_string
|
||||
assert_equal expected_time, record.bonus_time
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
|
||||
|
||||
record.bonus_time = ''
|
||||
assert_nil record.bonus_time
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_time_zone_aware_time_with_dst
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
current_time = Time.zone.local(2014, 06, 15, 10)
|
||||
record = @target.new(bonus_time: current_time)
|
||||
time_before_save = record.bonus_time
|
||||
|
||||
record.save
|
||||
record.reload
|
||||
|
||||
assert_equal time_before_save, record.bonus_time
|
||||
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
|
||||
end
|
||||
end
|
||||
|
||||
def test_removing_time_zone_aware_types
|
||||
with_time_zone_aware_types(:datetime) do
|
||||
in_time_zone "Pacific Time (US & Canada)" do
|
||||
record = @target.new(bonus_time: "10:00:00")
|
||||
expected_time = Time.utc(2000, 01, 01, 10)
|
||||
|
||||
assert_equal expected_time, record.bonus_time
|
||||
assert record.bonus_time.utc?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
|
||||
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
|
||||
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
|
||||
@ -908,6 +949,14 @@ def new_topic_like_ar_class(&block)
|
||||
klass
|
||||
end
|
||||
|
||||
def with_time_zone_aware_types(*types)
|
||||
old_types = ActiveRecord::Base.time_zone_aware_types
|
||||
ActiveRecord::Base.time_zone_aware_types = types
|
||||
yield
|
||||
ensure
|
||||
ActiveRecord::Base.time_zone_aware_types = old_types
|
||||
end
|
||||
|
||||
def cached_columns
|
||||
Topic.columns.map(&:name)
|
||||
end
|
||||
|
@ -30,6 +30,9 @@
|
||||
# Quote "type" if it's a reserved word for the current connection.
|
||||
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type')
|
||||
|
||||
# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends.
|
||||
ActiveRecord::Base.time_zone_aware_types << :time
|
||||
|
||||
def current_adapter?(*types)
|
||||
types.any? do |type|
|
||||
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
|
||||
|
@ -250,8 +250,8 @@ def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attr
|
||||
}
|
||||
topic = Topic.find(1)
|
||||
topic.attributes = attributes
|
||||
assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
|
||||
assert topic.bonus_time.utc?
|
||||
assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
|
||||
assert_not topic.bonus_time.utc?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user