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 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in freeform.gemspec
4
+ gemspec
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,9 @@
1
+ require 'active_model'
2
+
3
+ class ValidateableArray < Array
4
+ include ::ActiveModel::Validations
5
+
6
+ def initialize(array)
7
+ @array = array
8
+ end
9
+ 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,14 @@
1
+ require 'active_model'
2
+
3
+ class ValidateableObject
4
+ include ::ActiveModel::Validations
5
+
6
+ def initialize(object)
7
+ @object = object
8
+ end
9
+
10
+ private
11
+ def method_missing(method, *args, &block)
12
+ @object.send(method, *args, &block)
13
+ end
14
+ 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