activemodel 6.0.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 +7 -0
- data/CHANGELOG.md +172 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +247 -0
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +517 -0
- data/lib/active_model/attribute_mutation_tracker.rb +178 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attribute_set/builder.rb +124 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attributes.rb +138 -0
- data/lib/active_model/callbacks.rb +156 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +280 -0
- data/lib/active_model/errors.rb +601 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -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 +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +334 -0
- data/lib/active_model/railtie.rb +20 -0
- data/lib/active_model/secure_password.rb +128 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +147 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +47 -0
- data/lib/active_model/type/date.rb +53 -0
- data/lib/active_model/type/date_time.rb +47 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +34 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +44 -0
- data/lib/active_model/type/helpers/time_value.rb +81 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +58 -0
- data/lib/active_model/type/registry.rb +62 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +47 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +437 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +102 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -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 +114 -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 +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- metadata +125 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Value
|
6
|
+
attr_reader :precision, :scale, :limit
|
7
|
+
|
8
|
+
def initialize(precision: nil, limit: nil, scale: nil)
|
9
|
+
@precision = precision
|
10
|
+
@scale = scale
|
11
|
+
@limit = limit
|
12
|
+
end
|
13
|
+
|
14
|
+
def type # :nodoc:
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts a value from database input to the appropriate ruby type. The
|
18
|
+
# return value of this method will be returned from
|
19
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
20
|
+
# implementation just calls Value#cast.
|
21
|
+
#
|
22
|
+
# +value+ The raw input, as provided from the database.
|
23
|
+
def deserialize(value)
|
24
|
+
cast(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Type casts a value from user input (e.g. from a setter). This value may
|
28
|
+
# be a string from the form builder, or a ruby object passed to a setter.
|
29
|
+
# There is currently no way to differentiate between which source it came
|
30
|
+
# from.
|
31
|
+
#
|
32
|
+
# The return value of this method will be returned from
|
33
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
|
34
|
+
# Value#cast_value.
|
35
|
+
#
|
36
|
+
# +value+ The raw input, as provided to the attribute setter.
|
37
|
+
def cast(value)
|
38
|
+
cast_value(value) unless value.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Casts a value from the ruby type to a type that the database knows how
|
42
|
+
# to understand. The returned value from this method should be a
|
43
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
44
|
+
# +nil+.
|
45
|
+
def serialize(value)
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Type casts a value for schema dumping. This method is private, as we are
|
50
|
+
# hoping to remove it entirely.
|
51
|
+
def type_cast_for_schema(value) # :nodoc:
|
52
|
+
value.inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
# These predicates are not documented, as I need to look further into
|
56
|
+
# their use, and see if they can be removed entirely.
|
57
|
+
def binary? # :nodoc:
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Determines whether a value has changed for dirty checking. +old_value+
|
62
|
+
# and +new_value+ will always be type-cast. Types should not need to
|
63
|
+
# override this method.
|
64
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
65
|
+
old_value != new_value
|
66
|
+
end
|
67
|
+
|
68
|
+
# Determines whether the mutable value has been modified since it was
|
69
|
+
# read. Returns +false+ by default. If your type returns an object
|
70
|
+
# which could be mutated, you should override this method. You will need
|
71
|
+
# to either:
|
72
|
+
#
|
73
|
+
# - pass +new_value+ to Value#serialize and compare it to
|
74
|
+
# +raw_old_value+
|
75
|
+
#
|
76
|
+
# or
|
77
|
+
#
|
78
|
+
# - pass +raw_old_value+ to Value#deserialize and compare it to
|
79
|
+
# +new_value+
|
80
|
+
#
|
81
|
+
# +raw_old_value+ The original value, before being passed to
|
82
|
+
# +deserialize+.
|
83
|
+
#
|
84
|
+
# +new_value+ The current value, after type casting.
|
85
|
+
def changed_in_place?(raw_old_value, new_value)
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def value_constructed_by_mass_assignment?(_value) # :nodoc:
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def force_equality?(_value) # :nodoc:
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def map(value) # :nodoc:
|
98
|
+
yield value
|
99
|
+
end
|
100
|
+
|
101
|
+
def ==(other)
|
102
|
+
self.class == other.class &&
|
103
|
+
precision == other.precision &&
|
104
|
+
scale == other.scale &&
|
105
|
+
limit == other.limit
|
106
|
+
end
|
107
|
+
alias eql? ==
|
108
|
+
|
109
|
+
def hash
|
110
|
+
[self.class, precision, scale, limit].hash
|
111
|
+
end
|
112
|
+
|
113
|
+
def assert_valid_value(*)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Convenience method for types which do not need separate type casting
|
119
|
+
# behavior for user and database inputs. Called by Value#cast for
|
120
|
+
# values except +nil+.
|
121
|
+
def cast_value(value) # :doc:
|
122
|
+
value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,437 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
# == Active \Model \Validations
|
7
|
+
#
|
8
|
+
# Provides a full validation framework to your objects.
|
9
|
+
#
|
10
|
+
# A minimal implementation could be:
|
11
|
+
#
|
12
|
+
# class Person
|
13
|
+
# include ActiveModel::Validations
|
14
|
+
#
|
15
|
+
# attr_accessor :first_name, :last_name
|
16
|
+
#
|
17
|
+
# validates_each :first_name, :last_name do |record, attr, value|
|
18
|
+
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Which provides you with the full standard validation stack that you
|
23
|
+
# know from Active Record:
|
24
|
+
#
|
25
|
+
# person = Person.new
|
26
|
+
# person.valid? # => true
|
27
|
+
# person.invalid? # => false
|
28
|
+
#
|
29
|
+
# person.first_name = 'zoolander'
|
30
|
+
# person.valid? # => false
|
31
|
+
# person.invalid? # => true
|
32
|
+
# person.errors.messages # => {first_name:["starts with z."]}
|
33
|
+
#
|
34
|
+
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
|
35
|
+
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
|
36
|
+
# object, so there is no need for you to do this manually.
|
37
|
+
module Validations
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
included do
|
41
|
+
extend ActiveModel::Naming
|
42
|
+
extend ActiveModel::Callbacks
|
43
|
+
extend ActiveModel::Translation
|
44
|
+
|
45
|
+
extend HelperMethods
|
46
|
+
include HelperMethods
|
47
|
+
|
48
|
+
attr_accessor :validation_context
|
49
|
+
private :validation_context=
|
50
|
+
define_callbacks :validate, scope: :name
|
51
|
+
|
52
|
+
class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
# Validates each attribute against a block.
|
57
|
+
#
|
58
|
+
# class Person
|
59
|
+
# include ActiveModel::Validations
|
60
|
+
#
|
61
|
+
# attr_accessor :first_name, :last_name
|
62
|
+
#
|
63
|
+
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
|
64
|
+
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Options:
|
69
|
+
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
70
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
71
|
+
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
72
|
+
# <tt>on: :custom_validation_context</tt> or
|
73
|
+
# <tt>on: [:create, :custom_validation_context]</tt>)
|
74
|
+
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
75
|
+
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
76
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
77
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
78
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
79
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
80
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
81
|
+
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
82
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
83
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
84
|
+
# value.
|
85
|
+
def validates_each(*attr_names, &block)
|
86
|
+
validates_with BlockValidator, _merge_attributes(attr_names), &block
|
87
|
+
end
|
88
|
+
|
89
|
+
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
|
90
|
+
|
91
|
+
# Adds a validation method or block to the class. This is useful when
|
92
|
+
# overriding the +validate+ instance method becomes too unwieldy and
|
93
|
+
# you're looking for more descriptive declaration of your validations.
|
94
|
+
#
|
95
|
+
# This can be done with a symbol pointing to a method:
|
96
|
+
#
|
97
|
+
# class Comment
|
98
|
+
# include ActiveModel::Validations
|
99
|
+
#
|
100
|
+
# validate :must_be_friends
|
101
|
+
#
|
102
|
+
# def must_be_friends
|
103
|
+
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# With a block which is passed with the current record to be validated:
|
108
|
+
#
|
109
|
+
# class Comment
|
110
|
+
# include ActiveModel::Validations
|
111
|
+
#
|
112
|
+
# validate do |comment|
|
113
|
+
# comment.must_be_friends
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# def must_be_friends
|
117
|
+
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# Or with a block where self points to the current record to be validated:
|
122
|
+
#
|
123
|
+
# class Comment
|
124
|
+
# include ActiveModel::Validations
|
125
|
+
#
|
126
|
+
# validate do
|
127
|
+
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# Note that the return value of validation methods is not relevant.
|
132
|
+
# It's not possible to halt the validate callback chain.
|
133
|
+
#
|
134
|
+
# Options:
|
135
|
+
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
136
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
137
|
+
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
138
|
+
# <tt>on: :custom_validation_context</tt> or
|
139
|
+
# <tt>on: [:create, :custom_validation_context]</tt>)
|
140
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
141
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
142
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
143
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
144
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
145
|
+
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
146
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
147
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
148
|
+
# value.
|
149
|
+
#
|
150
|
+
# NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
|
151
|
+
#
|
152
|
+
def validate(*args, &block)
|
153
|
+
options = args.extract_options!
|
154
|
+
|
155
|
+
if args.all? { |arg| arg.is_a?(Symbol) }
|
156
|
+
options.each_key do |k|
|
157
|
+
unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
|
158
|
+
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if options.key?(:on)
|
164
|
+
options = options.dup
|
165
|
+
options[:on] = Array(options[:on])
|
166
|
+
options[:if] = Array(options[:if])
|
167
|
+
options[:if].unshift ->(o) {
|
168
|
+
!(options[:on] & Array(o.validation_context)).empty?
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
set_callback(:validate, *args, options, &block)
|
173
|
+
end
|
174
|
+
|
175
|
+
# List all validators that are being used to validate the model using
|
176
|
+
# +validates_with+ method.
|
177
|
+
#
|
178
|
+
# class Person
|
179
|
+
# include ActiveModel::Validations
|
180
|
+
#
|
181
|
+
# validates_with MyValidator
|
182
|
+
# validates_with OtherValidator, on: :create
|
183
|
+
# validates_with StrictValidator, strict: true
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# Person.validators
|
187
|
+
# # => [
|
188
|
+
# # #<MyValidator:0x007fbff403e808 @options={}>,
|
189
|
+
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
|
190
|
+
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
|
191
|
+
# # ]
|
192
|
+
def validators
|
193
|
+
_validators.values.flatten.uniq
|
194
|
+
end
|
195
|
+
|
196
|
+
# Clears all of the validators and validations.
|
197
|
+
#
|
198
|
+
# Note that this will clear anything that is being used to validate
|
199
|
+
# the model for both the +validates_with+ and +validate+ methods.
|
200
|
+
# It clears the validators that are created with an invocation of
|
201
|
+
# +validates_with+ and the callbacks that are set by an invocation
|
202
|
+
# of +validate+.
|
203
|
+
#
|
204
|
+
# class Person
|
205
|
+
# include ActiveModel::Validations
|
206
|
+
#
|
207
|
+
# validates_with MyValidator
|
208
|
+
# validates_with OtherValidator, on: :create
|
209
|
+
# validates_with StrictValidator, strict: true
|
210
|
+
# validate :cannot_be_robot
|
211
|
+
#
|
212
|
+
# def cannot_be_robot
|
213
|
+
# errors.add(:base, 'A person cannot be a robot') if person_is_robot
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# Person.validators
|
218
|
+
# # => [
|
219
|
+
# # #<MyValidator:0x007fbff403e808 @options={}>,
|
220
|
+
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
|
221
|
+
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
|
222
|
+
# # ]
|
223
|
+
#
|
224
|
+
# If one runs <tt>Person.clear_validators!</tt> and then checks to see what
|
225
|
+
# validators this class has, you would obtain:
|
226
|
+
#
|
227
|
+
# Person.validators # => []
|
228
|
+
#
|
229
|
+
# Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
|
230
|
+
# so that:
|
231
|
+
#
|
232
|
+
# Person._validate_callbacks.empty? # => true
|
233
|
+
#
|
234
|
+
def clear_validators!
|
235
|
+
reset_callbacks(:validate)
|
236
|
+
_validators.clear
|
237
|
+
end
|
238
|
+
|
239
|
+
# List all validators that are being used to validate a specific attribute.
|
240
|
+
#
|
241
|
+
# class Person
|
242
|
+
# include ActiveModel::Validations
|
243
|
+
#
|
244
|
+
# attr_accessor :name , :age
|
245
|
+
#
|
246
|
+
# validates_presence_of :name
|
247
|
+
# validates_inclusion_of :age, in: 0..99
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
# Person.validators_on(:name)
|
251
|
+
# # => [
|
252
|
+
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
|
253
|
+
# # ]
|
254
|
+
def validators_on(*attributes)
|
255
|
+
attributes.flat_map do |attribute|
|
256
|
+
_validators[attribute.to_sym]
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
|
261
|
+
#
|
262
|
+
# class Person
|
263
|
+
# include ActiveModel::Validations
|
264
|
+
#
|
265
|
+
# attr_accessor :name
|
266
|
+
# end
|
267
|
+
#
|
268
|
+
# User.attribute_method?(:name) # => true
|
269
|
+
# User.attribute_method?(:age) # => false
|
270
|
+
def attribute_method?(attribute)
|
271
|
+
method_defined?(attribute)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Copy validators on inheritance.
|
275
|
+
def inherited(base) #:nodoc:
|
276
|
+
dup = _validators.dup
|
277
|
+
base._validators = dup.each { |k, v| dup[k] = v.dup }
|
278
|
+
super
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Clean the +Errors+ object if instance is duped.
|
283
|
+
def initialize_dup(other) #:nodoc:
|
284
|
+
@errors = nil
|
285
|
+
super
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns the +Errors+ object that holds all information about attribute
|
289
|
+
# error messages.
|
290
|
+
#
|
291
|
+
# class Person
|
292
|
+
# include ActiveModel::Validations
|
293
|
+
#
|
294
|
+
# attr_accessor :name
|
295
|
+
# validates_presence_of :name
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
# person = Person.new
|
299
|
+
# person.valid? # => false
|
300
|
+
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
|
301
|
+
def errors
|
302
|
+
@errors ||= Errors.new(self)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Runs all the specified validations and returns +true+ if no errors were
|
306
|
+
# added otherwise +false+.
|
307
|
+
#
|
308
|
+
# class Person
|
309
|
+
# include ActiveModel::Validations
|
310
|
+
#
|
311
|
+
# attr_accessor :name
|
312
|
+
# validates_presence_of :name
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# person = Person.new
|
316
|
+
# person.name = ''
|
317
|
+
# person.valid? # => false
|
318
|
+
# person.name = 'david'
|
319
|
+
# person.valid? # => true
|
320
|
+
#
|
321
|
+
# Context can optionally be supplied to define which callbacks to test
|
322
|
+
# against (the context is defined on the validations using <tt>:on</tt>).
|
323
|
+
#
|
324
|
+
# class Person
|
325
|
+
# include ActiveModel::Validations
|
326
|
+
#
|
327
|
+
# attr_accessor :name
|
328
|
+
# validates_presence_of :name, on: :new
|
329
|
+
# end
|
330
|
+
#
|
331
|
+
# person = Person.new
|
332
|
+
# person.valid? # => true
|
333
|
+
# person.valid?(:new) # => false
|
334
|
+
def valid?(context = nil)
|
335
|
+
current_context, self.validation_context = validation_context, context
|
336
|
+
errors.clear
|
337
|
+
run_validations!
|
338
|
+
ensure
|
339
|
+
self.validation_context = current_context
|
340
|
+
end
|
341
|
+
|
342
|
+
alias_method :validate, :valid?
|
343
|
+
|
344
|
+
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
|
345
|
+
# added, +false+ otherwise.
|
346
|
+
#
|
347
|
+
# class Person
|
348
|
+
# include ActiveModel::Validations
|
349
|
+
#
|
350
|
+
# attr_accessor :name
|
351
|
+
# validates_presence_of :name
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# person = Person.new
|
355
|
+
# person.name = ''
|
356
|
+
# person.invalid? # => true
|
357
|
+
# person.name = 'david'
|
358
|
+
# person.invalid? # => false
|
359
|
+
#
|
360
|
+
# Context can optionally be supplied to define which callbacks to test
|
361
|
+
# against (the context is defined on the validations using <tt>:on</tt>).
|
362
|
+
#
|
363
|
+
# class Person
|
364
|
+
# include ActiveModel::Validations
|
365
|
+
#
|
366
|
+
# attr_accessor :name
|
367
|
+
# validates_presence_of :name, on: :new
|
368
|
+
# end
|
369
|
+
#
|
370
|
+
# person = Person.new
|
371
|
+
# person.invalid? # => false
|
372
|
+
# person.invalid?(:new) # => true
|
373
|
+
def invalid?(context = nil)
|
374
|
+
!valid?(context)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
378
|
+
# no errors are found, raises +ValidationError+ otherwise.
|
379
|
+
#
|
380
|
+
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
381
|
+
# some <tt>:on</tt> option will only run in the specified context.
|
382
|
+
def validate!(context = nil)
|
383
|
+
valid?(context) || raise_validation_error
|
384
|
+
end
|
385
|
+
|
386
|
+
# Hook method defining how an attribute value should be retrieved. By default
|
387
|
+
# this is assumed to be an instance named after the attribute. Override this
|
388
|
+
# method in subclasses should you need to retrieve the value for a given
|
389
|
+
# attribute differently:
|
390
|
+
#
|
391
|
+
# class MyClass
|
392
|
+
# include ActiveModel::Validations
|
393
|
+
#
|
394
|
+
# def initialize(data = {})
|
395
|
+
# @data = data
|
396
|
+
# end
|
397
|
+
#
|
398
|
+
# def read_attribute_for_validation(key)
|
399
|
+
# @data[key]
|
400
|
+
# end
|
401
|
+
# end
|
402
|
+
alias :read_attribute_for_validation :send
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
def run_validations!
|
407
|
+
_run_validate_callbacks
|
408
|
+
errors.empty?
|
409
|
+
end
|
410
|
+
|
411
|
+
def raise_validation_error # :doc:
|
412
|
+
raise(ValidationError.new(self))
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# = Active Model ValidationError
|
417
|
+
#
|
418
|
+
# Raised by <tt>validate!</tt> when the model is invalid. Use the
|
419
|
+
# +model+ method to retrieve the record which did not validate.
|
420
|
+
#
|
421
|
+
# begin
|
422
|
+
# complex_operation_that_internally_calls_validate!
|
423
|
+
# rescue ActiveModel::ValidationError => invalid
|
424
|
+
# puts invalid.model.errors
|
425
|
+
# end
|
426
|
+
class ValidationError < StandardError
|
427
|
+
attr_reader :model
|
428
|
+
|
429
|
+
def initialize(model)
|
430
|
+
@model = model
|
431
|
+
errors = @model.errors.full_messages.join(", ")
|
432
|
+
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
|