activemodel 4.2.0 → 6.1.0
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 +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
3
4
|
|
4
5
|
module ActiveModel
|
5
6
|
# == Active \Model \Serialization
|
@@ -31,16 +32,14 @@ module ActiveModel
|
|
31
32
|
# of the attributes hash's keys. In order to override this behavior, take a look
|
32
33
|
# at the private method +read_attribute_for_serialization+.
|
33
34
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# <tt>ActiveModel::Serialization</tt
|
37
|
-
# explicitly include it.
|
35
|
+
# ActiveModel::Serializers::JSON module automatically includes
|
36
|
+
# the <tt>ActiveModel::Serialization</tt> module, so there is no need to
|
37
|
+
# explicitly include <tt>ActiveModel::Serialization</tt>.
|
38
38
|
#
|
39
|
-
# A minimal implementation including
|
39
|
+
# A minimal implementation including JSON would be:
|
40
40
|
#
|
41
41
|
# class Person
|
42
42
|
# include ActiveModel::Serializers::JSON
|
43
|
-
# include ActiveModel::Serializers::Xml
|
44
43
|
#
|
45
44
|
# attr_accessor :name
|
46
45
|
#
|
@@ -55,13 +54,11 @@ module ActiveModel
|
|
55
54
|
# person.serializable_hash # => {"name"=>nil}
|
56
55
|
# person.as_json # => {"name"=>nil}
|
57
56
|
# person.to_json # => "{\"name\":null}"
|
58
|
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
59
57
|
#
|
60
58
|
# person.name = "Bob"
|
61
59
|
# person.serializable_hash # => {"name"=>"Bob"}
|
62
60
|
# person.as_json # => {"name"=>"Bob"}
|
63
61
|
# person.to_json # => "{\"name\":\"Bob\"}"
|
64
|
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
65
62
|
#
|
66
63
|
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
|
67
64
|
# <tt>:include</tt>. The following are all valid examples:
|
@@ -94,20 +91,51 @@ module ActiveModel
|
|
94
91
|
# person.serializable_hash(except: :name) # => {"age"=>22}
|
95
92
|
# person.serializable_hash(methods: :capitalized_name)
|
96
93
|
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
|
94
|
+
#
|
95
|
+
# Example with <tt>:include</tt> option
|
96
|
+
#
|
97
|
+
# class User
|
98
|
+
# include ActiveModel::Serializers::JSON
|
99
|
+
# attr_accessor :name, :notes # Emulate has_many :notes
|
100
|
+
# def attributes
|
101
|
+
# {'name' => nil}
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# class Note
|
106
|
+
# include ActiveModel::Serializers::JSON
|
107
|
+
# attr_accessor :title, :text
|
108
|
+
# def attributes
|
109
|
+
# {'title' => nil, 'text' => nil}
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# note = Note.new
|
114
|
+
# note.title = 'Battle of Austerlitz'
|
115
|
+
# note.text = 'Some text here'
|
116
|
+
#
|
117
|
+
# user = User.new
|
118
|
+
# user.name = 'Napoleon'
|
119
|
+
# user.notes = [note]
|
120
|
+
#
|
121
|
+
# user.serializable_hash
|
122
|
+
# # => {"name" => "Napoleon"}
|
123
|
+
# user.serializable_hash(include: { notes: { only: 'title' }})
|
124
|
+
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
|
97
125
|
def serializable_hash(options = nil)
|
98
|
-
options ||= {}
|
99
|
-
|
100
126
|
attribute_names = attributes.keys
|
127
|
+
|
128
|
+
return serializable_attributes(attribute_names) if options.blank?
|
129
|
+
|
101
130
|
if only = options[:only]
|
102
131
|
attribute_names &= Array(only).map(&:to_s)
|
103
132
|
elsif except = options[:except]
|
104
133
|
attribute_names -= Array(except).map(&:to_s)
|
105
134
|
end
|
106
135
|
|
107
|
-
hash =
|
108
|
-
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
|
136
|
+
hash = serializable_attributes(attribute_names)
|
109
137
|
|
110
|
-
Array(options[:methods]).each { |m| hash[m.to_s] = send(m)
|
138
|
+
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
|
111
139
|
|
112
140
|
serializable_add_includes(options) do |association, records, opts|
|
113
141
|
hash[association.to_s] = if records.respond_to?(:to_ary)
|
@@ -121,7 +149,6 @@ module ActiveModel
|
|
121
149
|
end
|
122
150
|
|
123
151
|
private
|
124
|
-
|
125
152
|
# Hook method defining how an attribute value should be retrieved for
|
126
153
|
# serialization. By default this is assumed to be an instance named after
|
127
154
|
# the attribute. Override this method in subclasses should you need to
|
@@ -140,6 +167,10 @@ module ActiveModel
|
|
140
167
|
# end
|
141
168
|
alias :read_attribute_for_serialization :send
|
142
169
|
|
170
|
+
def serializable_attributes(attribute_names)
|
171
|
+
attribute_names.index_with { |n| read_attribute_for_serialization(n) }
|
172
|
+
end
|
173
|
+
|
143
174
|
# Add associations specified via the <tt>:include</tt> option.
|
144
175
|
#
|
145
176
|
# Expects a block that takes as arguments:
|
@@ -150,7 +181,7 @@ module ActiveModel
|
|
150
181
|
return unless includes = options[:include]
|
151
182
|
|
152
183
|
unless includes.is_a?(Hash)
|
153
|
-
includes = Hash[Array(includes).
|
184
|
+
includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
|
154
185
|
end
|
155
186
|
|
156
187
|
includes.each do |association, opts|
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/json"
|
2
4
|
|
3
5
|
module ActiveModel
|
4
6
|
module Serializers
|
@@ -10,8 +12,7 @@ module ActiveModel
|
|
10
12
|
included do
|
11
13
|
extend ActiveModel::Naming
|
12
14
|
|
13
|
-
class_attribute :include_root_in_json
|
14
|
-
self.include_root_in_json = false
|
15
|
+
class_attribute :include_root_in_json, instance_writer: false, default: false
|
15
16
|
end
|
16
17
|
|
17
18
|
# Returns a hash representing the model. Some configuration can be
|
@@ -25,13 +26,13 @@ module ActiveModel
|
|
25
26
|
# user = User.find(1)
|
26
27
|
# user.as_json
|
27
28
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
28
|
-
# # "created_at" => "2006
|
29
|
+
# # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true}
|
29
30
|
#
|
30
31
|
# ActiveRecord::Base.include_root_in_json = true
|
31
32
|
#
|
32
33
|
# user.as_json
|
33
34
|
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
34
|
-
# # "created_at" => "2006
|
35
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
35
36
|
#
|
36
37
|
# This behavior can also be achieved by setting the <tt>:root</tt> option
|
37
38
|
# to +true+ as in:
|
@@ -39,7 +40,14 @@ module ActiveModel
|
|
39
40
|
# user = User.find(1)
|
40
41
|
# user.as_json(root: true)
|
41
42
|
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
42
|
-
# # "created_at" => "2006
|
43
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
44
|
+
#
|
45
|
+
# If you prefer, <tt>:root</tt> may also be set to a custom string key instead as in:
|
46
|
+
#
|
47
|
+
# user = User.find(1)
|
48
|
+
# user.as_json(root: "author")
|
49
|
+
# # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
50
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
43
51
|
#
|
44
52
|
# Without any +options+, the returned Hash will include all the model's
|
45
53
|
# attributes.
|
@@ -47,7 +55,7 @@ module ActiveModel
|
|
47
55
|
# user = User.find(1)
|
48
56
|
# user.as_json
|
49
57
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
50
|
-
# # "created_at" => "2006
|
58
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
|
51
59
|
#
|
52
60
|
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
|
53
61
|
# the attributes included, and work similar to the +attributes+ method.
|
@@ -62,14 +70,14 @@ module ActiveModel
|
|
62
70
|
#
|
63
71
|
# user.as_json(methods: :permalink)
|
64
72
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
65
|
-
# # "created_at" => "2006
|
73
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
66
74
|
# # "permalink" => "1-konata-izumi" }
|
67
75
|
#
|
68
76
|
# To include associations use <tt>:include</tt>:
|
69
77
|
#
|
70
78
|
# user.as_json(include: :posts)
|
71
79
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
72
|
-
# # "created_at" => "2006
|
80
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
73
81
|
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
|
74
82
|
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
|
75
83
|
#
|
@@ -80,7 +88,7 @@ module ActiveModel
|
|
80
88
|
# only: :body } },
|
81
89
|
# only: :title } })
|
82
90
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
83
|
-
# # "created_at" => "2006
|
91
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
84
92
|
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
|
85
93
|
# # "title" => "Welcome to the weblog" },
|
86
94
|
# # { "comments" => [ { "body" => "Don't think too hard" } ],
|
@@ -92,11 +100,12 @@ module ActiveModel
|
|
92
100
|
include_root_in_json
|
93
101
|
end
|
94
102
|
|
103
|
+
hash = serializable_hash(options).as_json
|
95
104
|
if root
|
96
105
|
root = model_name.element if root == true
|
97
|
-
{ root =>
|
106
|
+
{ root => hash }
|
98
107
|
else
|
99
|
-
|
108
|
+
hash
|
100
109
|
end
|
101
110
|
end
|
102
111
|
|
@@ -134,7 +143,7 @@ module ActiveModel
|
|
134
143
|
# person.name # => "bob"
|
135
144
|
# person.age # => 22
|
136
145
|
# person.awesome # => true
|
137
|
-
def from_json(json, include_root=include_root_in_json)
|
146
|
+
def from_json(json, include_root = include_root_in_json)
|
138
147
|
hash = ActiveSupport::JSON.decode(json)
|
139
148
|
hash = hash.values.first if include_root
|
140
149
|
self.attributes = hash
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ActiveModel
|
3
4
|
# == Active \Model \Translation
|
4
5
|
#
|
5
6
|
# Provides integration between your object and the Rails internationalization
|
@@ -31,7 +32,7 @@ module ActiveModel
|
|
31
32
|
# ActiveModel::Errors#full_messages and
|
32
33
|
# ActiveModel::Translation#human_attribute_name.
|
33
34
|
def lookup_ancestors
|
34
|
-
|
35
|
+
ancestors.select { |x| x.respond_to?(:model_name) }
|
35
36
|
end
|
36
37
|
|
37
38
|
# Transforms attribute names into a more human format, such as "First name"
|
@@ -45,7 +46,7 @@ module ActiveModel
|
|
45
46
|
parts = attribute.to_s.split(".")
|
46
47
|
attribute = parts.pop
|
47
48
|
namespace = parts.join("/") unless parts.empty?
|
48
|
-
attributes_scope = "#{
|
49
|
+
attributes_scope = "#{i18n_scope}.attributes"
|
49
50
|
|
50
51
|
if namespace
|
51
52
|
defaults = lookup_ancestors.map do |klass|
|
@@ -63,7 +64,7 @@ module ActiveModel
|
|
63
64
|
defaults << attribute.humanize
|
64
65
|
|
65
66
|
options[:default] = defaults
|
66
|
-
I18n.translate(defaults.shift, options)
|
67
|
+
I18n.translate(defaults.shift, **options)
|
67
68
|
end
|
68
69
|
end
|
69
70
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Binary < Value # :nodoc:
|
6
|
+
def type
|
7
|
+
:binary
|
8
|
+
end
|
9
|
+
|
10
|
+
def binary?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast(value)
|
15
|
+
if value.is_a?(Data)
|
16
|
+
value.to_s
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize(value)
|
23
|
+
return if value.nil?
|
24
|
+
Data.new(super)
|
25
|
+
end
|
26
|
+
|
27
|
+
def changed_in_place?(raw_old_value, value)
|
28
|
+
old_value = deserialize(raw_old_value)
|
29
|
+
old_value != value
|
30
|
+
end
|
31
|
+
|
32
|
+
class Data # :nodoc:
|
33
|
+
def initialize(value)
|
34
|
+
@value = value.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
alias_method :to_str, :to_s
|
41
|
+
|
42
|
+
def hex
|
43
|
+
@value.unpack1("H*")
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
other == to_s || super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# == Active \Model \Type \Boolean
|
6
|
+
#
|
7
|
+
# A class that behaves like a boolean type, including rules for coercion of user input.
|
8
|
+
#
|
9
|
+
# === Coercion
|
10
|
+
# Values set from user input will first be coerced into the appropriate ruby type.
|
11
|
+
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
|
12
|
+
#
|
13
|
+
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
|
14
|
+
# - Empty strings are coerced to +nil+
|
15
|
+
# - All other values will be coerced to +true+
|
16
|
+
class Boolean < Value
|
17
|
+
FALSE_VALUES = [
|
18
|
+
false, 0,
|
19
|
+
"0", :"0",
|
20
|
+
"f", :f,
|
21
|
+
"F", :F,
|
22
|
+
"false", :false,
|
23
|
+
"FALSE", :FALSE,
|
24
|
+
"off", :off,
|
25
|
+
"OFF", :OFF,
|
26
|
+
].to_set.freeze
|
27
|
+
|
28
|
+
def type # :nodoc:
|
29
|
+
:boolean
|
30
|
+
end
|
31
|
+
|
32
|
+
def serialize(value) # :nodoc:
|
33
|
+
cast(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def cast_value(value)
|
38
|
+
if value == ""
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
!FALSE_VALUES.include?(value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Date < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
7
|
+
include Helpers::AcceptsMultiparameterTime.new
|
8
|
+
|
9
|
+
def type
|
10
|
+
:date
|
11
|
+
end
|
12
|
+
|
13
|
+
def type_cast_for_schema(value)
|
14
|
+
value.to_s(:db).inspect
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def cast_value(value)
|
19
|
+
if value.is_a?(::String)
|
20
|
+
return if value.empty?
|
21
|
+
fast_string_to_date(value) || fallback_string_to_date(value)
|
22
|
+
elsif value.respond_to?(:to_date)
|
23
|
+
value.to_date
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
30
|
+
def fast_string_to_date(string)
|
31
|
+
if string =~ ISO_DATE
|
32
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def fallback_string_to_date(string)
|
37
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_date(year, mon, mday)
|
41
|
+
unless year.nil? || (year == 0 && mon == 0 && mday == 0)
|
42
|
+
::Date.new(year, mon, mday) rescue nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def value_from_multiparameter_assignment(*)
|
47
|
+
time = super
|
48
|
+
time && new_date(time.year, time.mon, time.mday)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class DateTime < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
7
|
+
include Helpers::TimeValue
|
8
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
9
|
+
defaults: { 4 => 0, 5 => 0 }
|
10
|
+
)
|
11
|
+
|
12
|
+
def type
|
13
|
+
:datetime
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def cast_value(value)
|
18
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
19
|
+
return if value.empty?
|
20
|
+
|
21
|
+
fast_string_to_time(value) || fallback_string_to_time(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# '0.123456' -> 123456
|
25
|
+
# '1.123456' -> 123456
|
26
|
+
def microseconds(time)
|
27
|
+
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def fallback_string_to_time(string)
|
31
|
+
time_hash = ::Date._parse(string)
|
32
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
33
|
+
|
34
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
35
|
+
end
|
36
|
+
|
37
|
+
def value_from_multiparameter_assignment(values_hash)
|
38
|
+
missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
|
39
|
+
unless missing_parameters.empty?
|
40
|
+
raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class Decimal < Value # :nodoc:
|
8
|
+
include Helpers::Numeric
|
9
|
+
BIGDECIMAL_PRECISION = 18
|
10
|
+
|
11
|
+
def type
|
12
|
+
:decimal
|
13
|
+
end
|
14
|
+
|
15
|
+
def type_cast_for_schema(value)
|
16
|
+
value.to_s.inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def cast_value(value)
|
21
|
+
casted_value = \
|
22
|
+
case value
|
23
|
+
when ::Float
|
24
|
+
convert_float_to_big_decimal(value)
|
25
|
+
when ::Numeric
|
26
|
+
BigDecimal(value, precision || BIGDECIMAL_PRECISION)
|
27
|
+
when ::String
|
28
|
+
begin
|
29
|
+
value.to_d
|
30
|
+
rescue ArgumentError
|
31
|
+
BigDecimal(0)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if value.respond_to?(:to_d)
|
35
|
+
value.to_d
|
36
|
+
else
|
37
|
+
cast_value(value.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
apply_scale(casted_value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def convert_float_to_big_decimal(value)
|
45
|
+
if precision
|
46
|
+
BigDecimal(apply_scale(value), float_precision)
|
47
|
+
else
|
48
|
+
value.to_d
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def float_precision
|
53
|
+
if precision.to_i > ::Float::DIG + 1
|
54
|
+
::Float::DIG + 1
|
55
|
+
else
|
56
|
+
precision.to_i
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def apply_scale(value)
|
61
|
+
if scale
|
62
|
+
value.round(scale)
|
63
|
+
else
|
64
|
+
value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class Float < Value # :nodoc:
|
8
|
+
include Helpers::Numeric
|
9
|
+
|
10
|
+
def type
|
11
|
+
:float
|
12
|
+
end
|
13
|
+
|
14
|
+
def type_cast_for_schema(value)
|
15
|
+
return "::Float::NAN" if value.try(:nan?)
|
16
|
+
case value
|
17
|
+
when ::Float::INFINITY then "::Float::INFINITY"
|
18
|
+
when -::Float::INFINITY then "-::Float::INFINITY"
|
19
|
+
else super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def cast_value(value)
|
25
|
+
case value
|
26
|
+
when ::Float then value
|
27
|
+
when "Infinity" then ::Float::INFINITY
|
28
|
+
when "-Infinity" then -::Float::INFINITY
|
29
|
+
when "NaN" then ::Float::NAN
|
30
|
+
else value.to_f
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
class AcceptsMultiparameterTime < Module
|
7
|
+
module InstanceMethods
|
8
|
+
def serialize(value)
|
9
|
+
super(cast(value))
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast(value)
|
13
|
+
if value.is_a?(Hash)
|
14
|
+
value_from_multiparameter_assignment(value)
|
15
|
+
else
|
16
|
+
super(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_valid_value(value)
|
21
|
+
if value.is_a?(Hash)
|
22
|
+
value_from_multiparameter_assignment(value)
|
23
|
+
else
|
24
|
+
super(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def value_constructed_by_mass_assignment?(value)
|
29
|
+
value.is_a?(Hash)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(defaults: {})
|
34
|
+
include InstanceMethods
|
35
|
+
|
36
|
+
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
37
|
+
defaults.each do |k, v|
|
38
|
+
values_hash[k] ||= v
|
39
|
+
end
|
40
|
+
return unless values_hash[1] && values_hash[2] && values_hash[3]
|
41
|
+
values = values_hash.sort.map!(&:last)
|
42
|
+
::Time.public_send(default_timezone, *values)
|
43
|
+
end
|
44
|
+
private :value_from_multiparameter_assignment
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Mutable
|
7
|
+
def cast(value)
|
8
|
+
deserialize(serialize(value))
|
9
|
+
end
|
10
|
+
|
11
|
+
# +raw_old_value+ will be the `_before_type_cast` version of the
|
12
|
+
# value (likely a string). +new_value+ will be the current, type
|
13
|
+
# cast value.
|
14
|
+
def changed_in_place?(raw_old_value, new_value)
|
15
|
+
raw_old_value != serialize(new_value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|