validates_serialized 0.0.1.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|