validates_serialized 0.0.1.pre2
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 +15 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +46 -0
- data/README.md +151 -0
- data/lib/validates_serialized/each_validator.rb +20 -0
- data/lib/validates_serialized/validateable_array.rb +9 -0
- data/lib/validates_serialized/validateable_hash.rb +15 -0
- data/lib/validates_serialized/validateable_object.rb +14 -0
- data/lib/validates_serialized/validators/array_validator.rb +78 -0
- data/lib/validates_serialized/validators/hash_block_validator.rb +59 -0
- data/lib/validates_serialized/validators/hash_validator.rb +75 -0
- data/lib/validates_serialized/validators/object_block_validator.rb +58 -0
- data/lib/validates_serialized/validators/serialized_validator.rb +73 -0
- data/lib/validates_serialized/version.rb +3 -0
- data/lib/validates_serialized.rb +10 -0
- data/notes.md +5 -0
- data/spec/acceptance_spec.rb +95 -0
- data/spec/each_validator_spec.rb +47 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/validateable_array_spec.rb +15 -0
- data/spec/validateable_hash_spec.rb +54 -0
- data/spec/validateable_object_spec.rb +72 -0
- data/spec/validators/array_validator_spec.rb +182 -0
- data/spec/validators/hash_block_validator_spec.rb +98 -0
- data/spec/validators/hash_validator_spec.rb +183 -0
- data/spec/validators/object_block_validator_spec.rb +110 -0
- data/validates_serialized.gemspec +26 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NDEyNjVmMjdlZjJiMGZjZjhjNDk5Yjc1NGQ2MDY1MzkwNGFjZjgwMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NzMyMzBmNTBlYzk1MjY4ZTk3NGUxYTBkNDAxMTBhMzllMjIyZmM3YQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTdiOTliMTI2ZmMzOWY4Y2E3MWU4MjZmMTQ2YWY0NjBkMDU5NzViNGExOGNm
|
10
|
+
ZWRiMTE0Y2JiOTYwNzg2N2MyNGYwZDJlYzAwMzg4MzliZDM0YmI5OTczM2Zm
|
11
|
+
Nzc5NDcyOTU2ZjcxNTZlNWFhNzM2YWMxY2MzNzZkZWY1M2E5ZTI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NTc2Yjg0NzY2OTAwMjFiMzA2MjE0MWQ2ODdiZGI4NzY3MWFjYmI2MTcxZjg2
|
14
|
+
NjBlZGUwODVjMTZhYjZjMTc4MGExMTQ3ZWRhMjI4YTRjNGY2YTI5NmY3YTc3
|
15
|
+
ODFkY2QxYjMwNmE0YzI5NWE5ZjZlNGRhZDJjNGUyNmM1MjkyMzU=
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
validates_serialized_gem
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3-p545
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
validates_serialized (0.0.0)
|
5
|
+
activemodel (>= 4.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.8)
|
11
|
+
activesupport (= 4.1.8)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activesupport (4.1.8)
|
14
|
+
i18n (~> 0.6, >= 0.6.9)
|
15
|
+
json (~> 1.7, >= 1.7.7)
|
16
|
+
minitest (~> 5.1)
|
17
|
+
thread_safe (~> 0.1)
|
18
|
+
tzinfo (~> 1.1)
|
19
|
+
builder (3.2.2)
|
20
|
+
diff-lcs (1.2.5)
|
21
|
+
i18n (0.6.11)
|
22
|
+
json (1.8.1)
|
23
|
+
minitest (5.5.0)
|
24
|
+
rake (10.4.2)
|
25
|
+
rspec (2.99.0)
|
26
|
+
rspec-core (~> 2.99.0)
|
27
|
+
rspec-expectations (~> 2.99.0)
|
28
|
+
rspec-mocks (~> 2.99.0)
|
29
|
+
rspec-core (2.99.2)
|
30
|
+
rspec-expectations (2.99.2)
|
31
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
32
|
+
rspec-mocks (2.99.2)
|
33
|
+
sqlite3 (1.3.10)
|
34
|
+
thread_safe (0.3.4)
|
35
|
+
tzinfo (1.2.2)
|
36
|
+
thread_safe (~> 0.1)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
bundler (~> 1.5)
|
43
|
+
rake (~> 10.1)
|
44
|
+
rspec (~> 2.6)
|
45
|
+
sqlite3 (~> 1.3)
|
46
|
+
validates_serialized!
|
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# Validates Serialized
|
2
|
+
|
3
|
+
ActiveModel provides ways to validate attributes, and to serialize objects. This gem provides some ActiveModel extensions and syntactic sugar to simplify the process of validating those serialized objects.
|
4
|
+
|
5
|
+
This gem provides:
|
6
|
+
* A generic validation method that supports any serializable object
|
7
|
+
* A validation method for serialized hashes, to validate specific key values
|
8
|
+
* A validation method for serialized hashes, to support validating all hash values
|
9
|
+
* A validation method for serialized arrays, to support validating all array values
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'validates_serialized', '~> 0.0.1'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install validates_serialized
|
24
|
+
|
25
|
+
## Validating a generic object
|
26
|
+
|
27
|
+
Here we have an example, serializable class called 'Person' with a name and age attribute.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class Person
|
31
|
+
def initialize(h={})
|
32
|
+
h.each {|k,v| send("#{k}=",v)}
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
@name ||= nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def name=(val)
|
40
|
+
@name = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def age
|
44
|
+
@age ||= nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def age=(val)
|
48
|
+
@age = val
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Now we can serialize this object and validate its properties in another class
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class Family < ActiveRecord::Base
|
57
|
+
include ActiveModel
|
58
|
+
...
|
59
|
+
|
60
|
+
serialize :father, Person
|
61
|
+
validates_serialized :father do
|
62
|
+
validates :name, presence: true
|
63
|
+
validates :age, numericality: { greater_than: 21 }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
The validations will be run against the serialized object whenever validation hooks are fired. E.g.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# With valid serialized object
|
71
|
+
valid_father = Person.new(name: "Bob", age: 31)
|
72
|
+
family = Family.new(father: valid_father)
|
73
|
+
family.valid? #=> true
|
74
|
+
|
75
|
+
# With invalid serialized object
|
76
|
+
valid_father = Person.new(name: "Bob", age: 13)
|
77
|
+
family = Family.new(father: valid_father)
|
78
|
+
family.valid? #=> false
|
79
|
+
family.errors[:father] #=> ["age must be greater than 13"]
|
80
|
+
```
|
81
|
+
|
82
|
+
## Validating a serialized hash by keys
|
83
|
+
```ruby
|
84
|
+
class Comment < ActiveRecord::Base
|
85
|
+
include ActiveModel
|
86
|
+
...
|
87
|
+
|
88
|
+
serialize :metadata, Hash
|
89
|
+
validates_hash_keys :metadata do
|
90
|
+
validates :timestamp, presence: true
|
91
|
+
validates :locale, presence: true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# With valid hash
|
96
|
+
comment = Comment.new(metadata: { timestamp: Time.new(2014, 1, 1), locale: "Ohio" })
|
97
|
+
comment.valid? #=> true
|
98
|
+
|
99
|
+
# With invalid hash
|
100
|
+
comment = Comment.new(metadata: { timestamp: Time.new(2014, 1, 1), locale: nil })
|
101
|
+
comment.valid? #=> false
|
102
|
+
comment.errors[:metadata] #=> ["locale can't be blank"]
|
103
|
+
```
|
104
|
+
|
105
|
+
## Validating serialized hash values
|
106
|
+
```ruby
|
107
|
+
class Comment < ActiveRecord::Base
|
108
|
+
include ActiveModel
|
109
|
+
...
|
110
|
+
|
111
|
+
serialize :ratings, Hash
|
112
|
+
validates_hash_values_with :ratings, numericality: { greater_than: 0 }
|
113
|
+
end
|
114
|
+
|
115
|
+
# With valid hash
|
116
|
+
comment = Comment.new(ratings: { tom: 4, jim: 2 })
|
117
|
+
comment.valid? #=> true
|
118
|
+
|
119
|
+
# With invalid hash
|
120
|
+
comment = Comment.new(ratings: { tom: 4, jim: -1 })
|
121
|
+
comment.valid? #=> false
|
122
|
+
comment.errors[:ratings] #=> ["ratings must be greater than 0"]
|
123
|
+
```
|
124
|
+
|
125
|
+
## Validating a serialized array
|
126
|
+
```ruby
|
127
|
+
class Comment < ActiveRecord::Base
|
128
|
+
include ActiveModel
|
129
|
+
...
|
130
|
+
|
131
|
+
serialize :tags, Array
|
132
|
+
validates_array_values_with :tags, length: { minimum: 4 }
|
133
|
+
end
|
134
|
+
|
135
|
+
# With valid hash
|
136
|
+
comment = Comment.new(tags: ["ruby" "rails"])
|
137
|
+
comment.valid? #=> true
|
138
|
+
|
139
|
+
# With invalid hash
|
140
|
+
comment = Comment.new(tags: ["ruby" "rails", "ror"])
|
141
|
+
comment.valid? #=> false
|
142
|
+
comment.errors[:tags] #=> ["tags is too short (minimum is 4 characters)"]
|
143
|
+
```
|
144
|
+
|
145
|
+
## Contributing
|
146
|
+
|
147
|
+
1. Fork it
|
148
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
149
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
151
|
+
5. Create new Pull Request
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "active_model"
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# Extending ActiveModel's EachValidator to add +validate_each_in_array+
|
5
|
+
class EachValidator #:nodoc:
|
6
|
+
# Performs validation on the supplied record. By default this will call
|
7
|
+
# +validates_each+ to determine validity therefore subclasses should
|
8
|
+
# override +validates_each+ with validation logic.
|
9
|
+
def validate_each_in_array(record)
|
10
|
+
attributes.each do |attribute|
|
11
|
+
array = record.read_attribute_for_validation(attribute)
|
12
|
+
raise TypeError, "#{array} is not an array" unless array.is_a?(Array)
|
13
|
+
array.each_with_index do |value, index|
|
14
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
15
|
+
validate_each(record, :"#{attribute}[#{index}]", value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
class ValidateableHash < Hash
|
4
|
+
include ::ActiveModel::Validations
|
5
|
+
|
6
|
+
def initialize(hash)
|
7
|
+
@hash = hash
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def method_missing(method, *args, &block)
|
12
|
+
# Delegate all methods to access the hash
|
13
|
+
@hash[method]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class ArrayValidator < SerializedValidator #:nodoc:
|
4
|
+
protected
|
5
|
+
def validate_serialized(record, attribute, serialized)
|
6
|
+
serialized.each do |value|
|
7
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
8
|
+
validate_each(record, attribute, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_check!(value)
|
13
|
+
raise TypeError, "#{value} is not an Array" unless value.is_a?(Array)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def validates_array_values_with(*args, &block)
|
19
|
+
# TODO: It would be nice for these to all make a call to 'validates_serialized_with'
|
20
|
+
# so that there's more code re-use
|
21
|
+
# validates_serialized_with args.push(ArrayValidator), &block
|
22
|
+
|
23
|
+
options = args.extract_options!
|
24
|
+
options[:class] = self
|
25
|
+
|
26
|
+
validator = ArrayValidator.new(args, options, &block)
|
27
|
+
|
28
|
+
if validator.respond_to?(:attributes) && !validator.attributes.empty?
|
29
|
+
validator.attributes.each do |attribute|
|
30
|
+
_validators[attribute.to_sym] << validator
|
31
|
+
end
|
32
|
+
else
|
33
|
+
_validators[nil] << validator
|
34
|
+
end
|
35
|
+
|
36
|
+
validate(validator, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper to accept arguments in the style of the +validates+ class method
|
40
|
+
def validates_array_values(*attributes)
|
41
|
+
defaults = attributes.extract_options!.dup
|
42
|
+
validations = defaults.slice!(*_validates_default_keys)
|
43
|
+
|
44
|
+
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
|
45
|
+
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
|
46
|
+
|
47
|
+
defaults[:attributes] = attributes
|
48
|
+
|
49
|
+
validations.each do |key, options|
|
50
|
+
next unless options
|
51
|
+
key = "#{key.to_s.camelize}Validator"
|
52
|
+
|
53
|
+
begin
|
54
|
+
validator = key.include?('::') ? key.constantize : const_get(key)
|
55
|
+
rescue NameError
|
56
|
+
raise ArgumentError, "Unknown validator: '#{key}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
validates_array_values_with(validator, defaults.merge(_parse_validates_options(options)))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def validates_array_values!(*attributes)
|
64
|
+
options = attributes.extract_options!
|
65
|
+
options[:strict] = true
|
66
|
+
validates_array_values(*(attributes << options))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def validates_array_values_with(*args, &block)
|
71
|
+
options = args.extract_options!
|
72
|
+
args.each do |klass|
|
73
|
+
validator = ArrayValidator.new(args, options, &block)
|
74
|
+
validator.validate(self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class HashBlockValidator < BlockValidator #:nodoc:
|
4
|
+
def initialize(options, &block)
|
5
|
+
@block = block
|
6
|
+
@options = options
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
raise TypeError, "#{attribute} is not a Hash" unless value.is_a?(Hash)
|
13
|
+
error_hash = get_serialized_object_errors(value)
|
14
|
+
add_errors_to_record(record, attribute, error_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_serialized_object_errors(value)
|
18
|
+
serialized_object = ValidateableHash.new(value)
|
19
|
+
serialized_object.class_eval &@block
|
20
|
+
serialized_object.valid?
|
21
|
+
serialized_object.errors.messages
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_errors_to_record(record, attribute, error_hash)
|
25
|
+
error_hash.each_pair do |key, array|
|
26
|
+
text = array.join(", ")
|
27
|
+
message = "#{key} #{text}"
|
28
|
+
if exception = options[:strict]
|
29
|
+
exception = ActiveModel::StrictValidationFailed if exception == true
|
30
|
+
raise exception, message
|
31
|
+
end
|
32
|
+
record.errors.add(attribute, message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_message_from_error_hash(error_hash)
|
37
|
+
message = nil
|
38
|
+
error_hash.each_pair do |key, array|
|
39
|
+
message = array.join(", ")
|
40
|
+
end
|
41
|
+
message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
# Helper to accept arguments in the style of the +validates+ class method
|
47
|
+
def validates_hash_keys(*attr_names, &block)
|
48
|
+
raise ArgumentError, "You need to supply at least one attribute" if attr_names.empty?
|
49
|
+
validates_with HashBlockValidator, _merge_attributes(attr_names), &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def validates_hash_keys!(*attr_names, &block)
|
53
|
+
options = attr_names.extract_options!
|
54
|
+
options[:strict] = true
|
55
|
+
validates_hash_keys(*(attr_names << options), &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class HashValidator < SerializedValidator #:nodoc:
|
4
|
+
protected
|
5
|
+
def validate_serialized(record, attribute, serialized)
|
6
|
+
serialized.each_pair do |key, value|
|
7
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
8
|
+
validate_each(record, attribute, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_check!(value)
|
13
|
+
raise TypeError, "#{value} is not a Hash" unless value.is_a?(Hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def validates_hash_values_with(*args, &block)
|
19
|
+
options = args.extract_options!
|
20
|
+
options[:class] = self
|
21
|
+
|
22
|
+
validator = HashValidator.new(args, options, &block)
|
23
|
+
|
24
|
+
if validator.respond_to?(:attributes) && !validator.attributes.empty?
|
25
|
+
validator.attributes.each do |attribute|
|
26
|
+
_validators[attribute.to_sym] << validator
|
27
|
+
end
|
28
|
+
else
|
29
|
+
_validators[nil] << validator
|
30
|
+
end
|
31
|
+
|
32
|
+
validate(validator, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Helper to accept arguments in the style of the +validates+ class method
|
37
|
+
def validates_hash_values(*attributes)
|
38
|
+
defaults = attributes.extract_options!.dup
|
39
|
+
validations = defaults.slice!(*_validates_default_keys)
|
40
|
+
|
41
|
+
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
|
42
|
+
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
|
43
|
+
|
44
|
+
defaults[:attributes] = attributes
|
45
|
+
|
46
|
+
validations.each do |key, options|
|
47
|
+
next unless options
|
48
|
+
key = "#{key.to_s.camelize}Validator"
|
49
|
+
|
50
|
+
begin
|
51
|
+
validator = key.include?('::') ? key.constantize : const_get(key)
|
52
|
+
rescue NameError
|
53
|
+
raise ArgumentError, "Unknown validator: '#{key}'"
|
54
|
+
end
|
55
|
+
|
56
|
+
validates_hash_values_with(validator, defaults.merge(_parse_validates_options(options)))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validates_hash_values!(*attributes)
|
61
|
+
options = attributes.extract_options!
|
62
|
+
options[:strict] = true
|
63
|
+
validates_hash_values(*(attributes << options))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validates_hash_values_with(*args, &block)
|
68
|
+
options = args.extract_options!
|
69
|
+
args.each do |klass|
|
70
|
+
validator = HashValidator.new(args, options, &block)
|
71
|
+
validator.validate(self)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|