can_has_validations 1.0.2 → 1.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b18aa1039ebc20678d9781ff0e90d495253b6d40fa39c3ffec9ae69c03b6d298
4
- data.tar.gz: 6657e468172b9f6cdbd7f69f7d68a105c82661b521e5473d50fda8ca37387558
3
+ metadata.gz: 8ebceb28bd42a889a2d7b4d8d589dcdc25bf722d2a1f028de00dabeb92beb826
4
+ data.tar.gz: 6acf11da982dddf4702db8c386af8d0780d940f9b8c4273e881e18fb0a4bc12b
5
5
  SHA512:
6
- metadata.gz: 129fe04836706f5aa89ba7d1116b857375bd3088f1b3953247c045c8c75c385adc981d2d1e0189810aae40009689c4a18da2f714ac52b79869ba01ea3cd9f823
7
- data.tar.gz: 3454442e03e74108c632ac8f7dfed962850e36a5ce16afd595eb21b1855653edf8c4cefa4181f4eb7f65b308fc85a7d2341afcbab0a7d31ad268f4e0da000e8e
6
+ metadata.gz: 89327c90cb07c1663fbe9cfa245eb62d964499e3b05220de5bc5ed5443f153ea5613249f9e1afec65e5b29c345a43484dd7cafd1988d76a633c97c5ac62819b3
7
+ data.tar.gz: 177ce2aedb07dd299209d8099b52c3344cfa7f157682a3edc67ea27adce8853d51779138f191bb12171de5ab72976c5bd512c80eaa42b1f6575919c580b636a7
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2020 thomas morgan
1
+ Copyright 2012-2021 thomas morgan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -9,7 +9,10 @@ Validations provided:
9
9
  * Email
10
10
  * Existence
11
11
  * Grandparent
12
+ * Hash Keys
13
+ * Hash Values
12
14
  * Hostname
15
+ * IP address
13
16
  * Ordering
14
17
  * URL
15
18
  * Write Once
@@ -134,6 +137,42 @@ or the database foreign key (`:user_id`). You can also use any other field. The
134
137
  test is merely that they match, not that they are associations.
135
138
 
136
139
 
140
+ ## Hash Keys validator ##
141
+
142
+ Many databases now allow storing hashes. This validates the keys of those
143
+ hashes. It is conceptually the same as using the Array validator to validate
144
+ `hash_attribute.keys`.
145
+
146
+ It is able to use most existing validators that themselves work on individual
147
+ attribute values (including standard Rails validators, others that are part of
148
+ this gem, and likely many from other gems too).
149
+
150
+ By default it reports only one validation error per sub-validator, regardless
151
+ of how many keys fail validation. Use `multiple_errors: true` to report all
152
+ errors. See Array validator for more details.
153
+
154
+ validates :subjects,
155
+ hash_keys: {
156
+ format: /\A[a-z]+\z/,
157
+ # multiple_errors: true
158
+ }
159
+
160
+
161
+ ## Hash Values validator ##
162
+
163
+ This is the companion to the Hash Keys validator and validates hash values
164
+ instead. It is conceptually the same as using the Array validator to validate
165
+ `hash_attribute.values`.
166
+
167
+ See Hash Keys validator for more details.
168
+
169
+ validates :subjects,
170
+ hash_values: {
171
+ length: 3..100,
172
+ # multiple_errors: true
173
+ }
174
+
175
+
137
176
  ## Hostname validator ##
138
177
 
139
178
  Ensures an attribute is generally formatted as a hostname. It allows for any
@@ -147,6 +186,9 @@ TLD, so as to not fail as ICANN continues to add TLDs.
147
186
  # allows '_abc.example.com'
148
187
  validates :domain, hostname: {allow_underscore: true}
149
188
 
189
+ # allows '4.0/25.3.2.1.example.com'
190
+ validates :domain, hostname: {allow_slash: true}
191
+
150
192
  # allows 'a.example.com', but not 'example.com'
151
193
  validates :domain, hostname: {segments: 3..100}
152
194
 
@@ -158,6 +200,34 @@ TLD, so as to not fail as ICANN continues to add TLDs.
158
200
  # use 4 or 6 for ipv4 or ipv6 only
159
201
 
160
202
 
203
+ ## IP address validator ##
204
+
205
+ Ensures an attribute is generally formatted as a IP or IP block.
206
+
207
+ # allows '1.2.3.4' or '::1'
208
+ validates :ip, ipaddr: true
209
+
210
+ # allows '1.2.3.0/24' or '2001:db8::/64'
211
+ validates :cidr, ipaddr: {allow_block: true}
212
+
213
+ # if an ip block, the attribute must be fully contained within an allowed block.
214
+ # allows '10.0.0.1' and '10.0.0.0/24', but not '10.0.0.0/15'
215
+ validates :private_ip, ipaddr: {
216
+ allow_block: true,
217
+ within: [IPAddr.new('10.0.0.0/16'), '127.0.0.1']
218
+ # allowed IPs and blocks may be IPAddrs or Strings
219
+ }
220
+
221
+ # the inverse of :within
222
+ validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
223
+
224
+ # :within and :without may also be procs or method names
225
+ validates :ip, ipaddr: {
226
+ within: :some_method,
227
+ without: ->(record){ ... }
228
+ }
229
+
230
+
161
231
  ## Ordering validators ##
162
232
 
163
233
  Ensures two attribute values maintain a relative order to one another. This is
@@ -183,9 +253,9 @@ Always skips over nil values; use `:presence` to validate those.
183
253
 
184
254
  ## URL validator ##
185
255
 
186
- Ensure an attribute is generally formatted as a URL. If `addressable/uri` is
256
+ Ensures an attribute is generally formatted as a URL. If `addressable/uri` is
187
257
  already loaded, it will be used to parse IDN's. Additionally, allowed schemes
188
- can be specified; they default to ['http','https'].
258
+ can be specified; they default to `['http','https']`.
189
259
 
190
260
  validates :website, url: true
191
261
  validates :secure_url, url: {scheme: 'https'}
@@ -205,8 +275,8 @@ can be specified; they default to ['http','https'].
205
275
 
206
276
  ## Write Once validator ##
207
277
 
208
- Ensure that once a value is written, it becomes readonly. There are two uses
209
- for this.
278
+ Ensures that once a value is written, it becomes readonly. There are a few
279
+ uses for this.
210
280
 
211
281
  The first is as an equivalent to `attr_readonly :user_id` except that it also
212
282
  produces a validation error instead of silently ignoring the change as
@@ -221,6 +291,10 @@ sorts.
221
291
 
222
292
  validates :user_id, allow_nil: true, write_once: true
223
293
 
294
+ The third use is to allow a nil value, and treat the nil also as write-once.
295
+
296
+ validates :source, write_once: {immutable_nil: true}
297
+
224
298
 
225
299
  ## Error messages
226
300
 
@@ -232,6 +306,9 @@ Default messages are as follows:
232
306
  messages:
233
307
  invalid_email: "is an invalid email"
234
308
  invalid_hostname: "is an invalid hostname"
309
+ invalid_ip: "is an invalid IP"
310
+ ip_not_allowed: "is not an allowed IP"
311
+ single_ip_required: "must be a single IP"
235
312
  invalid_url: "is an invalid URL"
236
313
  unchangeable: "cannot be changed"
237
314
  before: "must be before %{attribute2}"
@@ -1,6 +1,6 @@
1
1
  require 'active_model/validations'
2
2
 
3
- %w(array email existence grandparent hostname ordering url write_once).each do |validator|
3
+ %w(array email existence grandparent hash_keys hash_values hostname ipaddr ordering url write_once).each do |validator|
4
4
  require "can_has_validations/validators/#{validator}_validator"
5
5
  end
6
6
 
@@ -3,8 +3,10 @@ en:
3
3
  messages:
4
4
  invalid_email: "is an invalid email"
5
5
  invalid_hostname: "is an invalid hostname"
6
+ invalid_ip: "is an invalid IP"
7
+ ip_not_allowed: "is not an allowed IP"
8
+ single_ip_required: "must be a single IP"
6
9
  invalid_url: "is an invalid URL"
7
10
  unchangeable: "cannot be changed"
8
11
  before: "must be before %{attribute2}"
9
12
  after: "must be after %{attribute2}"
10
-
@@ -0,0 +1,12 @@
1
+ es:
2
+ errors:
3
+ messages:
4
+ invalid_email: "es un correo electrónico no válido"
5
+ invalid_hostname: "es un nombre de sistema no válido"
6
+ invalid_ip: "es una IP no válida"
7
+ ip_not_allowed: "no es una IP permitida"
8
+ single_ip_required: "debe ser solo una IP"
9
+ invalid_url: "es una URL no válida"
10
+ unchangeable: "no se puede cambiar"
11
+ before: "debe ser antes de %{attribute2}"
12
+ after: "debe ser después de %{attribute2}"
@@ -19,6 +19,18 @@
19
19
  # multiple_errors: true,
20
20
  # format: /\A[^aeiou]*\z/
21
21
  # }
22
+ #
23
+ # the :if, :unless, and :on conditionals are not supported on sub-validators,
24
+ # but do work as normal on the :array validator itself.
25
+ #
26
+ # validates :permissions, if: :this_condition_works,
27
+ # array: {
28
+ # if: :this_condition_applies_to_permissions_but_not_each_element,
29
+ # inclusion: {
30
+ # in: %w(one two),
31
+ # unless: :conditions_on_subvalidators_are_ignored
32
+ # }
33
+ # }
22
34
 
23
35
  module ActiveModel
24
36
  module Validations
@@ -33,12 +45,17 @@ module ActiveModel
33
45
  defaults = @options.dup
34
46
  validations = defaults.slice!(*record_class.send(:_validates_default_keys), :attributes)
35
47
 
36
- raise ArgumentError, "You need to supply at least one array validation" if validations.empty?
48
+ raise ArgumentError, "You need to supply at least one validation for :#{kind}" if validations.empty?
37
49
 
38
50
  defaults[:attributes] = attributes
39
51
 
40
52
  @validators = validations.map do |key, sub_options|
41
53
  next unless sub_options
54
+
55
+ if (cond_keys = _parse_validates_options(sub_options).keys & %i(if on unless)).any?
56
+ raise ArgumentError, ":#{kind} does not support conditionals on sub-validators - found on #{key}: #{cond_keys.map(&:inspect).join(', ')}"
57
+ end
58
+
42
59
  key = "#{key.to_s.camelize}Validator"
43
60
 
44
61
  begin
@@ -47,7 +64,7 @@ module ActiveModel
47
64
  raise ArgumentError, "Unknown validator: '#{key}'"
48
65
  end
49
66
 
50
- klass.new(defaults.merge(_parse_validates_options(sub_options)))
67
+ klass.new(defaults.merge(_parse_validates_options(sub_options)).except(:if, :on, :unless))
51
68
  end
52
69
  end
53
70
 
@@ -56,7 +73,7 @@ module ActiveModel
56
73
  error_count = count_errors(record)
57
74
 
58
75
  Array(array_values).each do |value|
59
- validator.validate_each(record, attribute, value)
76
+ validate_one(validator, record, attribute, value)
60
77
 
61
78
  # to avoid repeating error messages, stop after a single error
62
79
  unless validator.options[:multiple_errors]
@@ -66,14 +83,21 @@ module ActiveModel
66
83
  end
67
84
  end
68
85
 
86
+ def validate_one(validator, record, attribute, value)
87
+ unless validator.is_a?(ExistenceValidator)
88
+ return if (value.nil? && validator.options[:allow_nil]) || (value.blank? && validator.options[:allow_blank])
89
+ end
90
+ validator.validate_each(record, attribute, value)
91
+ end
92
+
69
93
 
70
94
  private
71
95
 
72
96
  def count_errors(record)
73
- # more efficient than calling record.errors.size
74
- record.errors.messages.sum{|key, val| val.blank? ? 0 : val.size }
97
+ record.errors.count
75
98
  end
76
99
 
100
+ # copied from active_model/validations/validates.rb
77
101
  def _parse_validates_options(options)
78
102
  case options
79
103
  when TrueClass
@@ -13,7 +13,7 @@ module ActiveModel::Validations
13
13
 
14
14
  def validate_each(record, attribute, value)
15
15
  unless email_valid?(value)
16
- record.errors.add(attribute, :invalid_email, options.merge(value: value))
16
+ record.errors.add(attribute, :invalid_email, **options.merge(value: value))
17
17
  end
18
18
  end
19
19
 
@@ -16,11 +16,12 @@ module ActiveModel::Validations
16
16
  if cousin.nil?
17
17
  options[:allow_nil]
18
18
  else
19
- association.send(options[:parent]) == cousin.send(options[:parent])
19
+ association &&
20
+ association.send(options[:parent]) == cousin.send(options[:parent])
20
21
  end
21
22
  end
22
23
  unless all_match
23
- record.errors.add(attribute, :invalid, options.except(:allow_nil, :parent, :scope))
24
+ record.errors.add(attribute, :invalid, **options.except(:allow_nil, :parent, :scope))
24
25
  end
25
26
  end
26
27
  end
@@ -0,0 +1,30 @@
1
+ # validates each key of a hash attribute
2
+ #
3
+ # by default only allows the first error per validator, regardless of how many
4
+ # keys fail validation. this improves performance and avoids a bunch of
5
+ # repeating error messages.
6
+ # use `multiple_errors: true` on :hash_keys or a single sub-validator to
7
+ # enable the full set of errors. this is potentially useful if each error
8
+ # message will vary based upon each hash key.
9
+ #
10
+ # the :if, :unless, and :on conditionals are not supported on sub-validators,
11
+ # but do work as normal on the :hash_keys validator itself.
12
+ #
13
+ # usage:
14
+ # validates :subjects,
15
+ # hash_keys: {
16
+ # format: /\A[a-z]+\z/,
17
+ # # multiple_errors: true
18
+ # }
19
+
20
+ module ActiveModel
21
+ module Validations
22
+ class HashKeysValidator < ArrayValidator
23
+
24
+ def validate_each(record, attribute, hash)
25
+ super(record, attribute, Hash(hash).keys)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ # validates each value of a hash attribute
2
+ #
3
+ # by default only allows the first error per validator, regardless of how many
4
+ # values fail validation. this improves performance and avoids a bunch of
5
+ # repeating error messages.
6
+ # use `multiple_errors: true` on :hash_values or a single sub-validator to
7
+ # enable the full set of errors. this is potentially useful if each error
8
+ # message will vary based upon each hash value.
9
+ #
10
+ # the :if, :unless, and :on conditionals are not supported on sub-validators,
11
+ # but do work as normal on the :hash_values validator itself.
12
+ #
13
+ # usage:
14
+ # validates :subjects,
15
+ # hash_values: {
16
+ # length: 3..100,
17
+ # # multiple_errors: true
18
+ # }
19
+
20
+ module ActiveModel
21
+ module Validations
22
+ class HashValuesValidator < ArrayValidator
23
+
24
+ def initialize(options)
25
+ record_class = options[:class]
26
+ super
27
+ record_class.include HashValidatorKey
28
+ end
29
+
30
+ def validate_each(record, attribute, hash)
31
+ super(record, attribute, Array(Hash(hash)))
32
+ end
33
+
34
+ def validate_one(validator, record, attribute, key_and_value)
35
+ key, value = key_and_value
36
+ record.hash_validator_key = key
37
+ super(validator, record, attribute, value)
38
+ ensure
39
+ record.hash_validator_key = nil
40
+ end
41
+
42
+
43
+ module HashValidatorKey
44
+ def hash_validator_key
45
+ @_hash_validator_key
46
+ end
47
+
48
+ def hash_validator_key=(v)
49
+ @_hash_validator_key = v
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -18,6 +18,8 @@
18
18
  # allows '*.example.com'
19
19
  # validates :domain, hostname: {allow_underscore: true}
20
20
  # allows '_abc.example.com'
21
+ # validates :domain, hostname: {allow_slash: true}
22
+ # allows '4.0/25.3.2.1.example.com' # rfc2317
21
23
  # validates :domain, hostname: {segments: 3..100}
22
24
  # allows 'a.example.com', but not 'example.com'
23
25
  # validates :domain, hostname: {allow_ip: true} # or 4 or 6 for ipv4 or ipv6 only
@@ -30,9 +32,9 @@ require 'resolv'
30
32
  module ActiveModel::Validations
31
33
  class HostnameValidator < ActiveModel::EachValidator
32
34
 
33
- LABEL_REGEXP = /\A([a-zA-Z0-9_]([a-zA-Z0-9_-]+)?)?[a-zA-Z0-9]\z/
34
- FINAL_LABEL_REGEXP = /\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z/
35
- RESERVED_OPTIONS = %i(allow_ip allow_underscore allow_wildcard)
35
+ LABEL_REGEXP = %r{\A([a-zA-Z0-9_]([a-zA-Z0-9_/-]+)?)?[a-zA-Z0-9]\z}
36
+ FINAL_LABEL_REGEXP = %r{\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z}
37
+ RESERVED_OPTIONS = %i(allow_ip allow_slash allow_underscore allow_wildcard)
36
38
 
37
39
  def validate_each(record, attribute, value)
38
40
  case options[:allow_ip]
@@ -55,6 +57,7 @@ module ActiveModel::Validations
55
57
  is_valid &&= value.length <= 255
56
58
  is_valid &&= value !~ /\.\./
57
59
  is_valid &&= value !~ /_/ unless options[:allow_underscore]
60
+ is_valid &&= value !~ %r{/} unless options[:allow_slash]
58
61
  is_valid &&= labels.size.in? segments
59
62
  labels.each_with_index do |label, idx|
60
63
  is_valid &&= label.length <= 63
@@ -68,7 +71,7 @@ module ActiveModel::Validations
68
71
  end
69
72
 
70
73
  unless is_valid
71
- record.errors.add(attribute, :invalid_hostname, options.except(*RESERVED_OPTIONS).merge!(value: value))
74
+ record.errors.add(attribute, :invalid_hostname, **options.except(*RESERVED_OPTIONS).merge!(value: value))
72
75
  end
73
76
  end
74
77
 
@@ -0,0 +1,91 @@
1
+ # Ensure an attribute is generally formatted as a IP or IP block.
2
+ # eg: validates :ip, ipaddr: true
3
+ # validates :cidr, ipaddr: {allow_block: true}
4
+ # validates :private_ip, ipaddr: {within: [IPAddr.new('10.0.0.0/8'), '127.0.0.1']}
5
+ # ip must be within any one of the provided ips/blocks
6
+ # if ip is block, it must be fully contained within any one of the provided blocks
7
+ # validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
8
+ # ip must be outside all of the provided ips/blocks
9
+ # if ip is block, it must be fully outside all of the provided blocks
10
+
11
+ require 'ipaddr'
12
+
13
+ module ActiveModel::Validations
14
+ class IpaddrValidator < ActiveModel::EachValidator
15
+
16
+ def initialize(options)
17
+ options[:within] = normalize_within options[:within], :within
18
+ options[:without] = normalize_within options[:without], :without
19
+ super
20
+ end
21
+
22
+ def validate_each(record, attribute, value)
23
+ allowed_ips = resolve_array record, options[:within]
24
+ disallowed_ips = resolve_array record, options[:without]
25
+
26
+ ip = case value
27
+ when IPAddr
28
+ ip
29
+ when String
30
+ IPAddr.new(value) rescue nil
31
+ end
32
+ unless ip
33
+ record.errors.add(attribute, :invalid_ip, **options.merge(value: value))
34
+ return
35
+ end
36
+
37
+ if !options[:allow_block] && (ip.ipv4? && ip.prefix!=32 or ip.ipv6? && ip.prefix!=128)
38
+ record.errors.add(attribute, :single_ip_required, **options.merge(value: value))
39
+ end
40
+ if allowed_ips && allowed_ips.none?{|blk| blk.include? ip}
41
+ record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
42
+ elsif disallowed_ips && disallowed_ips.any?{|blk| blk.include? ip}
43
+ record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def normalize_within(val, key)
51
+ if val.nil? || val.respond_to?(:call) || val.is_a?(Symbol)
52
+ val
53
+ else
54
+ Array(val).flatten.map do |i|
55
+ case i
56
+ when IPAddr
57
+ i
58
+ when String
59
+ IPAddr.new i
60
+ else
61
+ raise "Unexpected value for #{key.inspect} : #{i}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def resolve_array(record, val)
68
+ res = if val.respond_to?(:call)
69
+ val.call(record)
70
+ elsif val.is_a?(Symbol)
71
+ record.send(val)
72
+ else
73
+ val
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ # tests for & fixes broken IPAddr <= 1.2.2
81
+ if IPAddr.new('192.168.2.0/32').include? '192.168.2.0/24'
82
+ # warn 'IPAddr <= 1.2.2 is broken; monkey-patching'
83
+ class IPAddr
84
+ def include?(other)
85
+ range = to_range
86
+ other = coerce_other(other).to_range
87
+ range.begin <= other.begin && range.end >= other.end
88
+ end
89
+ alias === include?
90
+ end
91
+ end
@@ -18,7 +18,7 @@ module ActiveModel::Validations
18
18
  next unless value && greater
19
19
  unless value < greater
20
20
  attr2 = attr_name.respond_to?(:call) ? 'it is' : record.class.human_attribute_name(attr_name)
21
- record.errors.add(attribute, :before, options.except(:before).merge!(attribute2: attr2, value: value))
21
+ record.errors.add(attribute, :before, **options.except(:before).merge!(attribute2: attr2, value: value))
22
22
  end
23
23
  end
24
24
  end
@@ -33,7 +33,7 @@ module ActiveModel::Validations
33
33
  next unless value && lesser
34
34
  unless value > lesser
35
35
  attr2 = attr_name.respond_to?(:call) ? 'it is' : record.class.human_attribute_name(attr_name)
36
- record.errors.add(attribute, :after, options.except(:after).merge!(attribute2: attr2, value: value))
36
+ record.errors.add(attribute, :after, **options.except(:after).merge!(attribute2: attr2, value: value))
37
37
  end
38
38
  end
39
39
  end
@@ -22,7 +22,7 @@ module ActiveModel::Validations
22
22
  u2 = u = URI.parse(value) rescue nil
23
23
  end
24
24
  if !u || !u2 || u.relative? || allowed_schemes.exclude?(u.scheme)
25
- record.errors.add(attribute, :invalid_url, options.merge(value: value, scheme: allowed_schemes))
25
+ record.errors.add(attribute, :invalid_url, **options.merge(value: value, scheme: allowed_schemes))
26
26
  end
27
27
  end
28
28
  end
@@ -12,15 +12,21 @@ module ActiveModel::Validations
12
12
  # nil to be allowed. prevent this.
13
13
  def validate(record)
14
14
  attributes.each do |attribute|
15
- value = record.read_attribute_for_validation(attribute)
16
- validate_each(record, attribute, value)
15
+ validate_each(record, attribute, nil)
17
16
  end
18
17
  end
19
18
 
20
- def validate_each(record, attribute, value)
21
- if record.persisted? && record.send("#{attribute}_changed?")
22
- if options[:immutable_nil] || !record.send("#{attribute}_was").nil?
23
- record.errors.add(attribute, :unchangeable, options.except(:immutable_nil).merge!(value: value))
19
+ def validate_each(record, attribute, _)
20
+ return unless record.persisted?
21
+ if !record.respond_to?("#{attribute}_changed?") && record.respond_to?("#{attribute}_id_changed?")
22
+ attr2 = "#{attribute}_id"
23
+ else
24
+ attr2 = attribute
25
+ end
26
+ if record.send("#{attr2}_changed?")
27
+ if options[:immutable_nil] || !record.send("#{attr2}_was").nil?
28
+ value = record.read_attribute_for_validation(attribute)
29
+ record.errors.add(attribute, :unchangeable, **options.except(:immutable_nil).merge!(value: value))
24
30
  end
25
31
  end
26
32
  end
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '1.0.2'
2
+ VERSION = '1.3.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: can_has_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thomas morgan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-06 00:00:00.000000000 Z
11
+ date: 2021-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '5.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.1'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '5.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.1'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: sqlite3
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -44,7 +44,8 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
- description: Assorted Rails 5.x-6.x validators.
47
+ description: 'Assorted Rails 5.x-6.x validators: Array, Email, Existence, Grandparent,
48
+ Hostname, IP address, Ordering, URL, Write Once'
48
49
  email:
49
50
  - tm@iprog.com
50
51
  executables: []
@@ -56,11 +57,15 @@ files:
56
57
  - Rakefile
57
58
  - lib/can_has_validations.rb
58
59
  - lib/can_has_validations/locale/en.yml
60
+ - lib/can_has_validations/locale/es.yml
59
61
  - lib/can_has_validations/validators/array_validator.rb
60
62
  - lib/can_has_validations/validators/email_validator.rb
61
63
  - lib/can_has_validations/validators/existence_validator.rb
62
64
  - lib/can_has_validations/validators/grandparent_validator.rb
65
+ - lib/can_has_validations/validators/hash_keys_validator.rb
66
+ - lib/can_has_validations/validators/hash_values_validator.rb
63
67
  - lib/can_has_validations/validators/hostname_validator.rb
68
+ - lib/can_has_validations/validators/ipaddr_validator.rb
64
69
  - lib/can_has_validations/validators/ordering_validator.rb
65
70
  - lib/can_has_validations/validators/url_validator.rb
66
71
  - lib/can_has_validations/validators/write_once_validator.rb
@@ -98,9 +103,10 @@ files:
98
103
  - test/dummy/script/rails
99
104
  - test/test_helper.rb
100
105
  homepage: https://github.com/zarqman/can_has_validations
101
- licenses: []
106
+ licenses:
107
+ - MIT
102
108
  metadata: {}
103
- post_install_message:
109
+ post_install_message:
104
110
  rdoc_options: []
105
111
  require_paths:
106
112
  - lib
@@ -115,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
121
  - !ruby/object:Gem::Version
116
122
  version: '0'
117
123
  requirements: []
118
- rubygems_version: 3.0.3
119
- signing_key:
124
+ rubygems_version: 3.0.9
125
+ signing_key:
120
126
  specification_version: 4
121
127
  summary: Assorted Rails 5.x-6.x validators
122
128
  test_files: