omg-activemodel 8.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +112 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- metadata +139 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/zones"
|
4
|
+
require "active_support/core_ext/time/zones"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Type
|
8
|
+
module Helpers # :nodoc: all
|
9
|
+
module TimeValue
|
10
|
+
def serialize_cast_value(value)
|
11
|
+
value = apply_seconds_precision(value)
|
12
|
+
|
13
|
+
if value.acts_like?(:time)
|
14
|
+
if is_utc?
|
15
|
+
value = value.getutc if !value.utc?
|
16
|
+
else
|
17
|
+
value = value.getlocal
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_seconds_precision(value)
|
25
|
+
return value unless precision && value.respond_to?(:nsec)
|
26
|
+
|
27
|
+
number_of_insignificant_digits = 9 - precision
|
28
|
+
round_power = 10**number_of_insignificant_digits
|
29
|
+
rounded_off_nsec = value.nsec % round_power
|
30
|
+
|
31
|
+
if rounded_off_nsec > 0
|
32
|
+
value.change(nsec: value.nsec - rounded_off_nsec)
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def type_cast_for_schema(value)
|
39
|
+
value.to_fs(:db).inspect
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_input_in_time_zone(value)
|
43
|
+
value.in_time_zone
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
48
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
49
|
+
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
50
|
+
|
51
|
+
if offset
|
52
|
+
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
53
|
+
return unless time
|
54
|
+
|
55
|
+
time -= offset unless offset == 0
|
56
|
+
is_utc? ? time : time.getlocal
|
57
|
+
elsif is_utc?
|
58
|
+
::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
59
|
+
else
|
60
|
+
::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ISO_DATETIME = /
|
65
|
+
\A
|
66
|
+
(\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T
|
67
|
+
(\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456
|
68
|
+
(?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00
|
69
|
+
\z
|
70
|
+
/x
|
71
|
+
|
72
|
+
if RUBY_VERSION >= "3.2"
|
73
|
+
if Time.new(2000, 1, 1, 0, 0, 0, "-00:00").yday != 1 # Early 3.2.x had a bug
|
74
|
+
# BUG: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0
|
75
|
+
# used to return an invalid Time object
|
76
|
+
# see: https://bugs.ruby-lang.org/issues/19292
|
77
|
+
def fast_string_to_time(string)
|
78
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
79
|
+
|
80
|
+
if is_utc?
|
81
|
+
::Time.at(::Time.new(string, in: "UTC"))
|
82
|
+
else
|
83
|
+
::Time.new(string)
|
84
|
+
end
|
85
|
+
rescue ArgumentError
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
else
|
89
|
+
def fast_string_to_time(string)
|
90
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
91
|
+
|
92
|
+
if is_utc?
|
93
|
+
::Time.new(string, in: "UTC")
|
94
|
+
else
|
95
|
+
::Time.new(string)
|
96
|
+
end
|
97
|
+
rescue ArgumentError
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
def fast_string_to_time(string)
|
103
|
+
return unless ISO_DATETIME =~ string
|
104
|
+
|
105
|
+
usec = $7.to_i
|
106
|
+
usec_len = $7&.length
|
107
|
+
if usec_len&.< 6
|
108
|
+
usec *= 10**(6 - usec_len)
|
109
|
+
end
|
110
|
+
|
111
|
+
if $8
|
112
|
+
offset = \
|
113
|
+
if $8 == "Z"
|
114
|
+
0
|
115
|
+
else
|
116
|
+
offset_h, offset_m = $8.to_i, $9.to_i
|
117
|
+
offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/time/zones"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
module Helpers # :nodoc: all
|
8
|
+
module Timezone
|
9
|
+
def is_utc?
|
10
|
+
if default = ::Time.zone_default
|
11
|
+
default.name == "UTC"
|
12
|
+
else
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_timezone
|
18
|
+
is_utc? ? :utc : :local
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/helpers/accepts_multiparameter_time"
|
4
|
+
require "active_model/type/helpers/numeric"
|
5
|
+
require "active_model/type/helpers/mutable"
|
6
|
+
require "active_model/type/helpers/time_value"
|
7
|
+
require "active_model/type/helpers/timezone"
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# = Active Model \ImmutableString \Type
|
6
|
+
#
|
7
|
+
# Attribute type to represent immutable strings. It casts incoming values to
|
8
|
+
# frozen strings.
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :name, :immutable_string
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# person = Person.new
|
17
|
+
# person.name = 1
|
18
|
+
#
|
19
|
+
# person.name # => "1"
|
20
|
+
# person.name.frozen? # => true
|
21
|
+
#
|
22
|
+
# Values are coerced to strings using their +to_s+ method. Boolean values
|
23
|
+
# are treated differently, however: +true+ will be cast to <tt>"t"</tt> and
|
24
|
+
# +false+ will be cast to <tt>"f"</tt>. These strings can be customized when
|
25
|
+
# declaring an attribute:
|
26
|
+
#
|
27
|
+
# class Person
|
28
|
+
# include ActiveModel::Attributes
|
29
|
+
#
|
30
|
+
# attribute :active, :immutable_string, true: "aye", false: "nay"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# person = Person.new
|
34
|
+
# person.active = true
|
35
|
+
#
|
36
|
+
# person.active # => "aye"
|
37
|
+
class ImmutableString < Value
|
38
|
+
def initialize(**args)
|
39
|
+
@true = -(args.delete(:true)&.to_s || "t")
|
40
|
+
@false = -(args.delete(:false)&.to_s || "f")
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
def type
|
45
|
+
:string
|
46
|
+
end
|
47
|
+
|
48
|
+
def serialize(value)
|
49
|
+
case value
|
50
|
+
when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s
|
51
|
+
when true then @true
|
52
|
+
when false then @false
|
53
|
+
else super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def serialize_cast_value(value) # :nodoc:
|
58
|
+
value
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def cast_value(value)
|
63
|
+
case value
|
64
|
+
when true then @true
|
65
|
+
when false then @false
|
66
|
+
else value.to_s.freeze
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# = Active Model \Integer \Type
|
6
|
+
#
|
7
|
+
# Attribute type for integer representation. This type is registered under
|
8
|
+
# the +:integer+ key.
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :age, :integer
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Values are cast using their +to_i+ method, except for blank strings, which
|
17
|
+
# are cast to +nil+. If a +to_i+ method is not defined or raises an error,
|
18
|
+
# the value will be cast to +nil+.
|
19
|
+
#
|
20
|
+
# person = Person.new
|
21
|
+
#
|
22
|
+
# person.age = "18"
|
23
|
+
# person.age # => 18
|
24
|
+
#
|
25
|
+
# person.age = ""
|
26
|
+
# person.age # => nil
|
27
|
+
#
|
28
|
+
# person.age = :not_an_integer
|
29
|
+
# person.age # => nil (because Symbol does not define #to_i)
|
30
|
+
#
|
31
|
+
# Serialization also works under the same principle. Non-numeric strings are
|
32
|
+
# serialized as +nil+, for example.
|
33
|
+
#
|
34
|
+
# Serialization also validates that the integer can be stored using a
|
35
|
+
# limited number of bytes. If it cannot, an ActiveModel::RangeError will be
|
36
|
+
# raised. The default limit is 4 bytes, and can be customized when declaring
|
37
|
+
# an attribute:
|
38
|
+
#
|
39
|
+
# class Person
|
40
|
+
# include ActiveModel::Attributes
|
41
|
+
#
|
42
|
+
# attribute :age, :integer, limit: 6
|
43
|
+
# end
|
44
|
+
class Integer < Value
|
45
|
+
include Helpers::Numeric
|
46
|
+
|
47
|
+
# Column storage size in bytes.
|
48
|
+
# 4 bytes means an integer as opposed to smallint etc.
|
49
|
+
DEFAULT_LIMIT = 4
|
50
|
+
|
51
|
+
def initialize(**)
|
52
|
+
super
|
53
|
+
@range = min_value...max_value
|
54
|
+
end
|
55
|
+
|
56
|
+
def type
|
57
|
+
:integer
|
58
|
+
end
|
59
|
+
|
60
|
+
def deserialize(value)
|
61
|
+
return if value.blank?
|
62
|
+
value.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
def serialize(value)
|
66
|
+
return if value.is_a?(::String) && non_numeric_string?(value)
|
67
|
+
ensure_in_range(super)
|
68
|
+
end
|
69
|
+
|
70
|
+
def serialize_cast_value(value) # :nodoc:
|
71
|
+
ensure_in_range(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def serializable?(value)
|
75
|
+
cast_value = cast(value)
|
76
|
+
in_range?(cast_value) || begin
|
77
|
+
yield cast_value if block_given?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
attr_reader :range
|
84
|
+
|
85
|
+
def in_range?(value)
|
86
|
+
!value || range.member?(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def cast_value(value)
|
90
|
+
value.to_i rescue nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def ensure_in_range(value)
|
94
|
+
unless in_range?(value)
|
95
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
96
|
+
end
|
97
|
+
value
|
98
|
+
end
|
99
|
+
|
100
|
+
def max_value
|
101
|
+
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
102
|
+
end
|
103
|
+
|
104
|
+
def min_value
|
105
|
+
-max_value
|
106
|
+
end
|
107
|
+
|
108
|
+
def _limit
|
109
|
+
limit || DEFAULT_LIMIT
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Registry # :nodoc:
|
6
|
+
def initialize
|
7
|
+
@registrations = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize_copy(other)
|
11
|
+
@registrations = @registrations.dup
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def register(type_name, klass = nil, &block)
|
16
|
+
unless block_given?
|
17
|
+
block = proc { |_, *args| klass.new(*args) }
|
18
|
+
block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
|
19
|
+
end
|
20
|
+
registrations[type_name] = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def lookup(symbol, ...)
|
24
|
+
registration = registrations[symbol]
|
25
|
+
|
26
|
+
if registration
|
27
|
+
registration.call(symbol, ...)
|
28
|
+
else
|
29
|
+
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
attr_reader :registrations
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module SerializeCastValue # :nodoc:
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def serialize_cast_value_compatible?
|
10
|
+
return @serialize_cast_value_compatible if defined?(@serialize_cast_value_compatible)
|
11
|
+
@serialize_cast_value_compatible = ancestors.index(instance_method(:serialize_cast_value).owner) <= ancestors.index(instance_method(:serialize).owner)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module DefaultImplementation
|
16
|
+
def serialize_cast_value(value)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(klass)
|
22
|
+
klass.include DefaultImplementation unless klass.method_defined?(:serialize_cast_value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.serialize(type, value)
|
26
|
+
# Using `type.equal?(type.itself_if_...)` is a performant way to also
|
27
|
+
# ensure that `type` is not just a DelegateClass instance (e.g.
|
28
|
+
# ActiveRecord::Type::Serialized) unintentionally delegating
|
29
|
+
# SerializeCastValue methods.
|
30
|
+
if type.equal?((type.itself_if_serialize_cast_value_compatible rescue nil))
|
31
|
+
type.serialize_cast_value(value)
|
32
|
+
else
|
33
|
+
type.serialize(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def itself_if_serialize_cast_value_compatible
|
38
|
+
self if self.class.serialize_cast_value_compatible?
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(...)
|
42
|
+
super
|
43
|
+
self.class.serialize_cast_value_compatible? # eagerly compute
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/immutable_string"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
# = Active Model \String \Type
|
8
|
+
#
|
9
|
+
# Attribute type for strings. It is registered under the +:string+ key.
|
10
|
+
#
|
11
|
+
# This class is a specialization of ActiveModel::Type::ImmutableString. It
|
12
|
+
# performs coercion in the same way, and can be configured in the same way.
|
13
|
+
# However, it accounts for mutable strings, so dirty tracking can properly
|
14
|
+
# check if a string has changed.
|
15
|
+
class String < ImmutableString
|
16
|
+
def changed_in_place?(raw_old_value, new_value)
|
17
|
+
if new_value.is_a?(::String)
|
18
|
+
raw_old_value != new_value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_immutable_string
|
23
|
+
ImmutableString.new(
|
24
|
+
true: @true,
|
25
|
+
false: @false,
|
26
|
+
limit: limit,
|
27
|
+
precision: precision,
|
28
|
+
scale: scale,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def cast_value(value)
|
34
|
+
case value
|
35
|
+
when ::String then ::String.new(value)
|
36
|
+
when true then @true
|
37
|
+
when false then @false
|
38
|
+
else value.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# = Active Model \Time \Type
|
6
|
+
#
|
7
|
+
# Attribute type for time of day representation. It is registered under the
|
8
|
+
# +:time+ key.
|
9
|
+
#
|
10
|
+
# class Event
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :start, :time
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# String values are parsed using the ISO 8601 datetime format, but are
|
17
|
+
# normalized to have a date of 2000-01-01 and be in the UTC time zone.
|
18
|
+
#
|
19
|
+
# event = Event.new
|
20
|
+
# event.start = "2004-10-25T01:23:45-06:00"
|
21
|
+
#
|
22
|
+
# event.start.class # => Time
|
23
|
+
# event.start # => 2000-01-01 07:23:45 UTC
|
24
|
+
#
|
25
|
+
# Partial time-only formats are also accepted.
|
26
|
+
#
|
27
|
+
# event.start = "00:01:02+03:00"
|
28
|
+
# event.start # => 1999-12-31 21:01:02 UTC
|
29
|
+
#
|
30
|
+
# The degree of sub-second precision can be customized when declaring an
|
31
|
+
# attribute:
|
32
|
+
#
|
33
|
+
# class Event
|
34
|
+
# include ActiveModel::Attributes
|
35
|
+
#
|
36
|
+
# attribute :start, :time, precision: 4
|
37
|
+
# end
|
38
|
+
class Time < Value
|
39
|
+
include Helpers::Timezone
|
40
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
41
|
+
defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
42
|
+
)
|
43
|
+
include Helpers::TimeValue
|
44
|
+
|
45
|
+
def type
|
46
|
+
:time
|
47
|
+
end
|
48
|
+
|
49
|
+
def user_input_in_time_zone(value)
|
50
|
+
return unless value.present?
|
51
|
+
|
52
|
+
case value
|
53
|
+
when ::String
|
54
|
+
value = "2000-01-01 #{value}"
|
55
|
+
time_hash = begin
|
56
|
+
::Date._parse(value)
|
57
|
+
rescue ArgumentError
|
58
|
+
end
|
59
|
+
|
60
|
+
return if time_hash.nil? || time_hash[:hour].nil?
|
61
|
+
when ::Time
|
62
|
+
value = value.change(year: 2000, day: 1, month: 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
super(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def cast_value(value)
|
70
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
71
|
+
return if value.blank?
|
72
|
+
|
73
|
+
dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
|
74
|
+
|
75
|
+
fast_string_to_time(dummy_time_value) || begin
|
76
|
+
time_hash = begin
|
77
|
+
::Date._parse(dummy_time_value)
|
78
|
+
rescue ArgumentError
|
79
|
+
end
|
80
|
+
|
81
|
+
return if time_hash.nil? || time_hash[:hour].nil?
|
82
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|