activemodel 4.2.11.3 → 5.0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -56
- data/MIT-LICENSE +1 -1
- data/README.rdoc +8 -16
- data/lib/active_model/attribute_assignment.rb +52 -0
- data/lib/active_model/attribute_methods.rb +16 -16
- data/lib/active_model/callbacks.rb +3 -3
- data/lib/active_model/conversion.rb +5 -5
- data/lib/active_model/dirty.rb +41 -40
- data/lib/active_model/errors.rb +175 -68
- data/lib/active_model/forbidden_attributes_protection.rb +3 -2
- data/lib/active_model/gem_version.rb +5 -5
- data/lib/active_model/lint.rb +32 -28
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +3 -4
- data/lib/active_model/naming.rb +5 -4
- data/lib/active_model/secure_password.rb +2 -9
- data/lib/active_model/serialization.rb +36 -9
- data/lib/active_model/type/big_integer.rb +13 -0
- data/lib/active_model/type/binary.rb +50 -0
- data/lib/active_model/type/boolean.rb +21 -0
- data/lib/active_model/type/date.rb +54 -0
- data/lib/active_model/type/date_time.rb +44 -0
- data/lib/active_model/type/decimal.rb +66 -0
- data/lib/active_model/type/decimal_without_scale.rb +11 -0
- data/lib/active_model/type/float.rb +25 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
- data/lib/active_model/type/helpers/mutable.rb +18 -0
- data/lib/active_model/type/helpers/numeric.rb +34 -0
- data/lib/active_model/type/helpers/time_value.rb +77 -0
- data/lib/active_model/type/helpers.rb +4 -0
- data/lib/active_model/type/immutable_string.rb +29 -0
- data/lib/active_model/type/integer.rb +66 -0
- data/lib/active_model/type/registry.rb +64 -0
- data/lib/active_model/type/string.rb +24 -0
- data/lib/active_model/type/text.rb +11 -0
- data/lib/active_model/type/time.rb +42 -0
- data/lib/active_model/type/unsigned_integer.rb +15 -0
- data/lib/active_model/type/value.rb +116 -0
- data/lib/active_model/type.rb +59 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +57 -9
- data/lib/active_model/validations/callbacks.rb +3 -3
- data/lib/active_model/validations/clusivity.rb +4 -3
- data/lib/active_model/validations/confirmation.rb +16 -4
- data/lib/active_model/validations/exclusion.rb +3 -1
- data/lib/active_model/validations/format.rb +1 -1
- data/lib/active_model/validations/helper_methods.rb +13 -0
- data/lib/active_model/validations/inclusion.rb +3 -3
- data/lib/active_model/validations/length.rb +49 -18
- data/lib/active_model/validations/numericality.rb +20 -11
- data/lib/active_model/validations/validates.rb +1 -1
- data/lib/active_model/validations/with.rb +0 -10
- data/lib/active_model/validations.rb +34 -1
- data/lib/active_model/validator.rb +5 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +4 -2
- metadata +31 -22
- data/lib/active_model/serializers/xml.rb +0 -238
@@ -17,8 +17,9 @@ module ActiveModel
|
|
17
17
|
module ForbiddenAttributesProtection # :nodoc:
|
18
18
|
protected
|
19
19
|
def sanitize_for_mass_assignment(attributes)
|
20
|
-
if attributes.respond_to?(:permitted?)
|
21
|
-
raise ActiveModel::ForbiddenAttributesError
|
20
|
+
if attributes.respond_to?(:permitted?)
|
21
|
+
raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
|
22
|
+
attributes.to_h
|
22
23
|
else
|
23
24
|
attributes
|
24
25
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module ActiveModel
|
2
|
-
# Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt>
|
2
|
+
# Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
|
3
3
|
def self.gem_version
|
4
4
|
Gem::Version.new VERSION::STRING
|
5
5
|
end
|
6
6
|
|
7
7
|
module VERSION
|
8
|
-
MAJOR =
|
9
|
-
MINOR =
|
10
|
-
TINY =
|
11
|
-
PRE = "
|
8
|
+
MAJOR = 5
|
9
|
+
MINOR = 0
|
10
|
+
TINY = 7
|
11
|
+
PRE = "2"
|
12
12
|
|
13
13
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
14
14
|
end
|
data/lib/active_model/lint.rb
CHANGED
@@ -21,28 +21,27 @@ module ActiveModel
|
|
21
21
|
# +self+.
|
22
22
|
module Tests
|
23
23
|
|
24
|
-
#
|
24
|
+
# Passes if the object's model responds to <tt>to_key</tt> and if calling
|
25
|
+
# this method returns +nil+ when the object is not persisted.
|
26
|
+
# Fails otherwise.
|
25
27
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# <tt>dom_id</tt> to generate unique ids for the object.
|
28
|
+
# <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
|
29
|
+
# of the model, and is used to a generate unique DOM id for the object.
|
29
30
|
def test_to_key
|
30
31
|
assert model.respond_to?(:to_key), "The model should respond to to_key"
|
31
32
|
def model.persisted?() false end
|
32
33
|
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
|
33
34
|
end
|
34
35
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# or +nil+ if <tt>model.persisted?</tt> is +false+.
|
36
|
+
# Passes if the object's model responds to <tt>to_param</tt> and if
|
37
|
+
# calling this method returns +nil+ when the object is not persisted.
|
38
|
+
# Fails otherwise.
|
39
39
|
#
|
40
|
+
# <tt>to_param</tt> is used to represent the object's key in URLs.
|
40
41
|
# Implementers can decide to either raise an exception or provide a
|
41
42
|
# default in case the record uses a composite primary key. There are no
|
42
43
|
# tests for this behavior in lint because it doesn't make sense to force
|
43
44
|
# any of the possible implementation strategies on the implementer.
|
44
|
-
# However, if the resource is not persisted?, then <tt>to_param</tt>
|
45
|
-
# should always return +nil+.
|
46
45
|
def test_to_param
|
47
46
|
assert model.respond_to?(:to_param), "The model should respond to to_param"
|
48
47
|
def model.to_key() [1] end
|
@@ -50,32 +49,34 @@ module ActiveModel
|
|
50
49
|
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
|
51
50
|
end
|
52
51
|
|
53
|
-
#
|
52
|
+
# Passes if the object's model responds to <tt>to_partial_path</tt> and if
|
53
|
+
# calling this method returns a string. Fails otherwise.
|
54
54
|
#
|
55
|
-
#
|
56
|
-
#
|
55
|
+
# <tt>to_partial_path</tt> is used for looking up partials. For example,
|
56
|
+
# a BlogPost model might return "blog_posts/blog_post".
|
57
57
|
def test_to_partial_path
|
58
58
|
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
|
59
59
|
assert_kind_of String, model.to_partial_path
|
60
60
|
end
|
61
61
|
|
62
|
-
#
|
62
|
+
# Passes if the object's model responds to <tt>persisted?</tt> and if
|
63
|
+
# calling this method returns either +true+ or +false+. Fails otherwise.
|
63
64
|
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# to the update action.
|
65
|
+
# <tt>persisted?</tt> is used when calculating the URL for an object.
|
66
|
+
# If the object is not persisted, a form for that object, for instance,
|
67
|
+
# will route to the create action. If it is persisted, a form for the
|
68
|
+
# object will route to the update action.
|
69
69
|
def test_persisted?
|
70
70
|
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
|
71
71
|
assert_boolean model.persisted?, "persisted?"
|
72
72
|
end
|
73
73
|
|
74
|
-
#
|
74
|
+
# Passes if the object's model responds to <tt>model_name</tt> both as
|
75
|
+
# an instance method and as a class method, and if calling this method
|
76
|
+
# returns a string with some convenience methods: <tt>:human</tt>,
|
77
|
+
# <tt>:singular</tt> and <tt>:plural</tt>.
|
75
78
|
#
|
76
|
-
#
|
77
|
-
# convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
|
78
|
-
# <tt>:plural</tt>. Check ActiveModel::Naming for more information.
|
79
|
+
# Check ActiveModel::Naming for more information.
|
79
80
|
def test_model_naming
|
80
81
|
assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
|
81
82
|
model_name = model.class.model_name
|
@@ -88,12 +89,15 @@ module ActiveModel
|
|
88
89
|
assert_equal model.model_name, model.class.model_name
|
89
90
|
end
|
90
91
|
|
91
|
-
#
|
92
|
+
# Passes if the object's model responds to <tt>errors</tt> and if calling
|
93
|
+
# <tt>[](attribute)</tt> on the result of this method returns an array.
|
94
|
+
# Fails otherwise.
|
92
95
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
96
|
+
# <tt>errors[attribute]</tt> is used to retrieve the errors of a model
|
97
|
+
# for a given attribute. If errors are present, the method should return
|
98
|
+
# an array of strings that are the errors for the attribute in question.
|
99
|
+
# If localization is used, the strings should be localized for the current
|
100
|
+
# locale. If no error is present, the method should return an empty array.
|
97
101
|
def test_errors_aref
|
98
102
|
assert model.respond_to?(:errors), "The model should respond to errors"
|
99
103
|
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
|
@@ -6,6 +6,7 @@ en:
|
|
6
6
|
# The values :model, :attribute and :value are always available for interpolation
|
7
7
|
# The value :count is available when applicable. Can be used for pluralization.
|
8
8
|
messages:
|
9
|
+
model_invalid: "Validation failed: %{errors}"
|
9
10
|
inclusion: "is not included in the list"
|
10
11
|
exclusion: "is reserved"
|
11
12
|
invalid: "is invalid"
|
@@ -16,7 +17,7 @@ en:
|
|
16
17
|
present: "must be blank"
|
17
18
|
too_long:
|
18
19
|
one: "is too long (maximum is 1 character)"
|
19
|
-
other: "is too long (maximum is %{count} characters)"
|
20
|
+
other: "is too long (maximum is %{count} characters)"
|
20
21
|
too_short:
|
21
22
|
one: "is too short (minimum is 1 character)"
|
22
23
|
other: "is too short (minimum is %{count} characters)"
|
data/lib/active_model/model.rb
CHANGED
@@ -57,6 +57,7 @@ module ActiveModel
|
|
57
57
|
# (see below).
|
58
58
|
module Model
|
59
59
|
extend ActiveSupport::Concern
|
60
|
+
include ActiveModel::AttributeAssignment
|
60
61
|
include ActiveModel::Validations
|
61
62
|
include ActiveModel::Conversion
|
62
63
|
|
@@ -75,10 +76,8 @@ module ActiveModel
|
|
75
76
|
# person = Person.new(name: 'bob', age: '18')
|
76
77
|
# person.name # => "bob"
|
77
78
|
# person.age # => "18"
|
78
|
-
def initialize(
|
79
|
-
|
80
|
-
self.public_send("#{attr}=", value)
|
81
|
-
end if params
|
79
|
+
def initialize(attributes={})
|
80
|
+
assign_attributes(attributes) if attributes
|
82
81
|
|
83
82
|
super()
|
84
83
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_support/core_ext/hash/except'
|
2
2
|
require 'active_support/core_ext/module/introspection'
|
3
3
|
require 'active_support/core_ext/module/remove_method'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
class Name
|
@@ -163,7 +164,7 @@ module ActiveModel
|
|
163
164
|
@route_key << "_index" if @plural == @singular
|
164
165
|
end
|
165
166
|
|
166
|
-
# Transform the model name into a more
|
167
|
+
# Transform the model name into a more human format, using I18n. By default,
|
167
168
|
# it will underscore then humanize the class name.
|
168
169
|
#
|
169
170
|
# class BlogPost
|
@@ -190,8 +191,8 @@ module ActiveModel
|
|
190
191
|
|
191
192
|
private
|
192
193
|
|
193
|
-
def _singularize(string
|
194
|
-
ActiveSupport::Inflector.underscore(string).tr('/',
|
194
|
+
def _singularize(string)
|
195
|
+
ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
|
195
196
|
end
|
196
197
|
end
|
197
198
|
|
@@ -212,7 +213,7 @@ module ActiveModel
|
|
212
213
|
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
|
213
214
|
#
|
214
215
|
# Providing the functionality that ActiveModel::Naming provides in your object
|
215
|
-
# is required to pass the Active Model Lint test. So either extending the
|
216
|
+
# is required to pass the \Active \Model Lint test. So either extending the
|
216
217
|
# provided method below, or rolling your own is required.
|
217
218
|
module Naming
|
218
219
|
def self.extended(base) #:nodoc:
|
@@ -26,7 +26,7 @@ module ActiveModel
|
|
26
26
|
# it). When this attribute has a +nil+ value, the validation will not be
|
27
27
|
# triggered.
|
28
28
|
#
|
29
|
-
# For further customizability, it is possible to
|
29
|
+
# For further customizability, it is possible to suppress the default
|
30
30
|
# validations by passing <tt>validations: false</tt> as an argument.
|
31
31
|
#
|
32
32
|
# Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
|
@@ -77,13 +77,6 @@ module ActiveModel
|
|
77
77
|
validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
78
78
|
validates_confirmation_of :password, allow_blank: true
|
79
79
|
end
|
80
|
-
|
81
|
-
# This code is necessary as long as the protected_attributes gem is supported.
|
82
|
-
if respond_to?(:attributes_protected_by_default)
|
83
|
-
def self.attributes_protected_by_default #:nodoc:
|
84
|
-
super + ['password_digest']
|
85
|
-
end
|
86
|
-
end
|
87
80
|
end
|
88
81
|
end
|
89
82
|
|
@@ -99,7 +92,7 @@ module ActiveModel
|
|
99
92
|
# user.authenticate('notright') # => false
|
100
93
|
# user.authenticate('mUc3m00RsqyRe') # => user
|
101
94
|
def authenticate(unencrypted_password)
|
102
|
-
BCrypt::Password.new(password_digest)
|
95
|
+
BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
|
103
96
|
end
|
104
97
|
|
105
98
|
attr_reader :password
|
@@ -31,16 +31,14 @@ module ActiveModel
|
|
31
31
|
# of the attributes hash's keys. In order to override this behavior, take a look
|
32
32
|
# at the private method +read_attribute_for_serialization+.
|
33
33
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# <tt>ActiveModel::Serialization</tt
|
37
|
-
# explicitly include it.
|
34
|
+
# ActiveModel::Serializers::JSON module automatically includes
|
35
|
+
# the <tt>ActiveModel::Serialization</tt> module, so there is no need to
|
36
|
+
# explicitly include <tt>ActiveModel::Serialization</tt>.
|
38
37
|
#
|
39
|
-
# A minimal implementation including
|
38
|
+
# A minimal implementation including JSON would be:
|
40
39
|
#
|
41
40
|
# class Person
|
42
41
|
# include ActiveModel::Serializers::JSON
|
43
|
-
# include ActiveModel::Serializers::Xml
|
44
42
|
#
|
45
43
|
# attr_accessor :name
|
46
44
|
#
|
@@ -55,13 +53,11 @@ module ActiveModel
|
|
55
53
|
# person.serializable_hash # => {"name"=>nil}
|
56
54
|
# person.as_json # => {"name"=>nil}
|
57
55
|
# person.to_json # => "{\"name\":null}"
|
58
|
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
59
56
|
#
|
60
57
|
# person.name = "Bob"
|
61
58
|
# person.serializable_hash # => {"name"=>"Bob"}
|
62
59
|
# person.as_json # => {"name"=>"Bob"}
|
63
60
|
# person.to_json # => "{\"name\":\"Bob\"}"
|
64
|
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
|
65
61
|
#
|
66
62
|
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
|
67
63
|
# <tt>:include</tt>. The following are all valid examples:
|
@@ -94,6 +90,37 @@ module ActiveModel
|
|
94
90
|
# person.serializable_hash(except: :name) # => {"age"=>22}
|
95
91
|
# person.serializable_hash(methods: :capitalized_name)
|
96
92
|
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
|
93
|
+
#
|
94
|
+
# Example with <tt>:include</tt> option
|
95
|
+
#
|
96
|
+
# class User
|
97
|
+
# include ActiveModel::Serializers::JSON
|
98
|
+
# attr_accessor :name, :notes # Emulate has_many :notes
|
99
|
+
# def attributes
|
100
|
+
# {'name' => nil}
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# class Note
|
105
|
+
# include ActiveModel::Serializers::JSON
|
106
|
+
# attr_accessor :title, :text
|
107
|
+
# def attributes
|
108
|
+
# {'title' => nil, 'text' => nil}
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# note = Note.new
|
113
|
+
# note.title = 'Battle of Austerlitz'
|
114
|
+
# note.text = 'Some text here'
|
115
|
+
#
|
116
|
+
# user = User.new
|
117
|
+
# user.name = 'Napoleon'
|
118
|
+
# user.notes = [note]
|
119
|
+
#
|
120
|
+
# user.serializable_hash
|
121
|
+
# # => {"name" => "Napoleon"}
|
122
|
+
# user.serializable_hash(include: { notes: { only: 'title' }})
|
123
|
+
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
|
97
124
|
def serializable_hash(options = nil)
|
98
125
|
options ||= {}
|
99
126
|
|
@@ -107,7 +134,7 @@ module ActiveModel
|
|
107
134
|
hash = {}
|
108
135
|
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
|
109
136
|
|
110
|
-
Array(options[:methods]).each { |m| hash[m.to_s] = send(m)
|
137
|
+
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
|
111
138
|
|
112
139
|
serializable_add_includes(options) do |association, records, opts|
|
113
140
|
hash[association.to_s] = if records.respond_to?(:to_ary)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Binary < Value # :nodoc:
|
4
|
+
def type
|
5
|
+
:binary
|
6
|
+
end
|
7
|
+
|
8
|
+
def binary?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast(value)
|
13
|
+
if value.is_a?(Data)
|
14
|
+
value.to_s
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize(value)
|
21
|
+
return if value.nil?
|
22
|
+
Data.new(super)
|
23
|
+
end
|
24
|
+
|
25
|
+
def changed_in_place?(raw_old_value, value)
|
26
|
+
old_value = deserialize(raw_old_value)
|
27
|
+
old_value != value
|
28
|
+
end
|
29
|
+
|
30
|
+
class Data # :nodoc:
|
31
|
+
def initialize(value)
|
32
|
+
@value = value.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@value
|
37
|
+
end
|
38
|
+
alias_method :to_str, :to_s
|
39
|
+
|
40
|
+
def hex
|
41
|
+
@value.unpack('H*')[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
other == to_s || super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Boolean < Value # :nodoc:
|
4
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
|
5
|
+
|
6
|
+
def type
|
7
|
+
:boolean
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def cast_value(value)
|
13
|
+
if value == ''
|
14
|
+
nil
|
15
|
+
else
|
16
|
+
!FALSE_VALUES.include?(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Date < Value # :nodoc:
|
4
|
+
include Helpers::AcceptsMultiparameterTime.new
|
5
|
+
|
6
|
+
def type
|
7
|
+
:date
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(value)
|
11
|
+
cast(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def type_cast_for_schema(value)
|
15
|
+
"'#{value.to_s(:db)}'"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def cast_value(value)
|
21
|
+
if value.is_a?(::String)
|
22
|
+
return if value.empty?
|
23
|
+
fast_string_to_date(value) || fallback_string_to_date(value)
|
24
|
+
elsif value.respond_to?(:to_date)
|
25
|
+
value.to_date
|
26
|
+
else
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
32
|
+
def fast_string_to_date(string)
|
33
|
+
if string =~ ISO_DATE
|
34
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def fallback_string_to_date(string)
|
39
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_date(year, mon, mday)
|
43
|
+
if year && year != 0
|
44
|
+
::Date.new(year, mon, mday) rescue nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def value_from_multiparameter_assignment(*)
|
49
|
+
time = super
|
50
|
+
time && time.to_date
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class DateTime < Value # :nodoc:
|
4
|
+
include Helpers::TimeValue
|
5
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
6
|
+
defaults: { 4 => 0, 5 => 0 }
|
7
|
+
)
|
8
|
+
|
9
|
+
def type
|
10
|
+
:datetime
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def cast_value(value)
|
16
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
17
|
+
return if value.empty?
|
18
|
+
|
19
|
+
fast_string_to_time(value) || fallback_string_to_time(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
# '0.123456' -> 123456
|
23
|
+
# '1.123456' -> 123456
|
24
|
+
def microseconds(time)
|
25
|
+
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def fallback_string_to_time(string)
|
29
|
+
time_hash = ::Date._parse(string)
|
30
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
31
|
+
|
32
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
33
|
+
end
|
34
|
+
|
35
|
+
def value_from_multiparameter_assignment(values_hash)
|
36
|
+
missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
|
37
|
+
if missing_parameter
|
38
|
+
raise ArgumentError, missing_parameter
|
39
|
+
end
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "bigdecimal/util"
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Decimal < Value # :nodoc:
|
6
|
+
include Helpers::Numeric
|
7
|
+
|
8
|
+
def type
|
9
|
+
:decimal
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_cast_for_schema(value)
|
13
|
+
value.to_s.inspect
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def cast_value(value)
|
19
|
+
casted_value = case value
|
20
|
+
when ::Float
|
21
|
+
convert_float_to_big_decimal(value)
|
22
|
+
when ::Numeric
|
23
|
+
BigDecimal(value, precision.to_i)
|
24
|
+
when ::String
|
25
|
+
begin
|
26
|
+
value.to_d
|
27
|
+
rescue ArgumentError
|
28
|
+
BigDecimal(0)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
if value.respond_to?(:to_d)
|
32
|
+
value.to_d
|
33
|
+
else
|
34
|
+
cast_value(value.to_s)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
apply_scale(casted_value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert_float_to_big_decimal(value)
|
42
|
+
if precision
|
43
|
+
BigDecimal(apply_scale(value), float_precision)
|
44
|
+
else
|
45
|
+
value.to_d
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def float_precision
|
50
|
+
if precision.to_i > ::Float::DIG + 1
|
51
|
+
::Float::DIG + 1
|
52
|
+
else
|
53
|
+
precision.to_i
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def apply_scale(value)
|
58
|
+
if scale
|
59
|
+
value.round(scale)
|
60
|
+
else
|
61
|
+
value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Float < Value # :nodoc:
|
4
|
+
include Helpers::Numeric
|
5
|
+
|
6
|
+
def type
|
7
|
+
:float
|
8
|
+
end
|
9
|
+
|
10
|
+
alias serialize cast
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def cast_value(value)
|
15
|
+
case value
|
16
|
+
when ::Float then value
|
17
|
+
when "Infinity" then ::Float::INFINITY
|
18
|
+
when "-Infinity" then -::Float::INFINITY
|
19
|
+
when "NaN" then ::Float::NAN
|
20
|
+
else value.to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
module Helpers
|
4
|
+
class AcceptsMultiparameterTime < Module # :nodoc:
|
5
|
+
def initialize(defaults: {})
|
6
|
+
define_method(:cast) do |value|
|
7
|
+
if value.is_a?(Hash)
|
8
|
+
value_from_multiparameter_assignment(value)
|
9
|
+
else
|
10
|
+
super(value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
define_method(:assert_valid_value) do |value|
|
15
|
+
if value.is_a?(Hash)
|
16
|
+
value_from_multiparameter_assignment(value)
|
17
|
+
else
|
18
|
+
super(value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
23
|
+
defaults.each do |k, v|
|
24
|
+
values_hash[k] ||= v
|
25
|
+
end
|
26
|
+
return unless values_hash[1] && values_hash[2] && values_hash[3]
|
27
|
+
values = values_hash.sort.map(&:last)
|
28
|
+
::Time.send(default_timezone, *values)
|
29
|
+
end
|
30
|
+
private :value_from_multiparameter_assignment
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|