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 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