validates-structure 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fabdbd7d01016e5ded1f1cfe683c8e8f998ef94d
4
+ data.tar.gz: f2dc477eb6004acdac22e983e914b677f2929778
5
+ SHA512:
6
+ metadata.gz: 0d74f74ac673327c6f134370c86ecfb210f8056974773d44c3fc43c71d164e5939c74a43cd70723a3857aa307403fb6dd020ce9b1ba3937e7309ee4428db2fe2
7
+ data.tar.gz: 8b790670773538b6890270f04d4ae621a2fa5e4c0bf36d49e5661bb11177f7956ea0d15ff9d07b1897a367b4580b2bc79264a563780e389dfb050552ca578b25
@@ -0,0 +1,20 @@
1
+ Copyright 2013 PugglePay
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,202 @@
1
+ Validates Structure
2
+ ===================
3
+
4
+ Validates Structure allows you to easily validate hash-structures using ActiveModel::Validations.
5
+
6
+
7
+ Dependencies
8
+ ------------
9
+ The gem works for ActiveModel 4 and above.
10
+
11
+
12
+ Installation
13
+ ------------
14
+ Simply add the gem to your gemfile:
15
+
16
+ ```ruby
17
+ gem 'validates-structure'
18
+ ```
19
+
20
+ and make sure your activemodel version is at least 4.0.0:
21
+
22
+ ```ruby
23
+ gem 'activemodel', '>=4.0.0'
24
+ ```
25
+
26
+ Remember to
27
+
28
+ ```ruby
29
+ require 'validates-structure'
30
+ ```
31
+
32
+ at the top of the file when defining a new structure.
33
+
34
+ Canonical Usage Example
35
+ ------------
36
+
37
+ ```ruby
38
+ require 'validates-structure'
39
+
40
+ class MyCanonicalValidator < ValidatesStructure::Validator
41
+ key 'foo', Hash do
42
+ key 'bar', Array do
43
+ value Integer, allow_nil: true
44
+ end
45
+ key 'baz', String, format: /\A[0-f]\z/
46
+ end
47
+ end
48
+
49
+ validator = MyCanonicalValidator.new({foo: {bar: [1, 2, nil, 'invalid']}})
50
+ validator.valid?
51
+ # => false
52
+ puts validator.errors.full_messages
53
+ # => /foo/bar[3] has class "String" but should be a "Integer"
54
+ # => /foo/baz is invalid
55
+ # => /foo/baz must not be nil
56
+ ```
57
+
58
+ Quick facts about Validates Structure
59
+ -------------------------------------
60
+ * Validates Structure uses ActiveModel::Validations to validate your hash.
61
+ * Validates Structure automatically validates the type of each declared entry and will also give an error when undeclared keys are present.
62
+ * You can validate that a value are true or false by using the `Boolean` class (even though there are no Boolean class i Ruby).
63
+ * You can make compound hashes by setting a subclass to ValidatesStructure::Validator as the class in a key or value declaration.
64
+ * It doesn't matter if your structure uses symbols or strings as hash keys.
65
+ * Just like when validating attributes in a model, you can use your own custom validations.
66
+
67
+ Examples
68
+ --------
69
+
70
+ ### Minimal example
71
+
72
+ ```ruby
73
+ class MySimpleValidator < ValidatesStructure::Validator
74
+ key 'apa', Integer
75
+ end
76
+
77
+ MySimpleValidator.new(apa: 3).valid?
78
+ # => true
79
+ ```
80
+
81
+ ### Boolean example
82
+
83
+ ```ruby
84
+ class MyBooleanValidator < ValidatesStructure::Validator
85
+ key 'apa', Boolean
86
+ end
87
+
88
+ MyBooleanValidator.new(apa: true).valid?
89
+ # => true
90
+ ```
91
+
92
+ ### Nested example
93
+
94
+ ```ruby
95
+ class MyNestedValidator < ValidatesStructure::Validator
96
+ key 'apa', Hash do
97
+ key 'bepa', String, presence: true
98
+ end
99
+ end
100
+
101
+ validator = MyNestedValidator.new(apa: { bepa: "" })
102
+ validator.valid?
103
+ # => false
104
+ puts validator.errors.full_messages
105
+ # => /apa/bepa can't be blank
106
+ ```
107
+
108
+ ### Array example
109
+
110
+ ```ruby
111
+ class MyArrayValidator < ValidatesStructure::Validator
112
+ key 'apa', Hash do
113
+ key 'bepa', Array do
114
+ value Integer
115
+ end
116
+ end
117
+ end
118
+
119
+ validator = MyArrayValidator.new(apa: { bepa: [1, 2, "3"] })
120
+ validator.valid?
121
+ # => true
122
+ puts validator.errors.full_messages
123
+ # => /apa/bepa[2] has class "String" but should be a "Integer"
124
+ ```
125
+
126
+ ### Compound example
127
+
128
+ ```ruby
129
+ class MyInnerValidator < ValidatesStructure::Validator
130
+ key 'bepa', Integer
131
+ end
132
+
133
+ class MyOuterValidator < ValidatesStructure::Validator
134
+ key 'apa', MyInnerValidator
135
+ end
136
+
137
+ MyOuterValidator.new(apa: { bepa: 3 }).valid?
138
+ # => true
139
+ ```
140
+
141
+ ### Custom validator example
142
+
143
+ ```ruby
144
+ class OddValidator < ActiveModel::EachValidator
145
+ def validate_each(record, attribute, value)
146
+ record.errors.add attribute, "can't be even." if value.even?
147
+ end
148
+ end
149
+
150
+ class MyCustomValidator < ValidatesStructure::Validator
151
+ key 'apa', Integer, odd: true
152
+ end
153
+
154
+ MyCustomValidator.new(apa: 3).valid?
155
+ # => true
156
+ ```
157
+
158
+
159
+ Documentation
160
+ -------------
161
+ This documentation is about the modules, classes, methods and options of ValidatesStructure. For documentation on ActiveModel::Validations see [the ActiveModel documentation.](http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates)
162
+
163
+ ### ValidatesStructure::Validator
164
+
165
+ #### self.key(index, klass, validations={}, &block)
166
+ Sets up a requirement on the form ```'index' => klass``` that are validated with _validations_ and containing children on the form specified in _&block_.
167
+
168
+ **Parameters**
169
+
170
+ _index_ - The string or symbol by which to retrieve the value
171
+
172
+ _klass_ - The required class of the value. If klass is a subclass of ValidatesStructure::Validator then the value is validated as specified in its definition.
173
+
174
+ _validations_ - A hash with [ActiveModel:Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html) on the same format as for the [validates](http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates) method.
175
+
176
+ _&block_ - A block of nested _key_ and/or _value_ declarations. Only applicable if klass is an Array or Hashe.
177
+
178
+
179
+ #### self.value(klass, validations={},&block)
180
+ Sets up a requirement like self.key but without an index. Useful for structures that are accessed by a numeric index such as Arrays.
181
+
182
+ **Parameters**
183
+
184
+ _klass_ - The required class of the value. If klass is a subclass of ValidatesStructure::Validator then the value is validated as specified in its definition.
185
+
186
+ _validations_ - A hash with [ActiveModel:Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html) on the same format as for the [validates](http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates) method.
187
+
188
+ _&block_ - A block of nested _key_ and/or _value_ declarations. Only applicable if klass is an Array or Hashe.
189
+
190
+
191
+ Some History
192
+ ------------
193
+ This project was initiated by the good fellows at [PugglePay](https://github.com/PugglePay) who felt there should be some easy, familiar way of validating their incoming json requests and wanted to share their solution with the world.
194
+
195
+
196
+ Contributing
197
+ ------------
198
+ 1. Fork the project
199
+ 2. Create a feature branch (git checkout -b my-new-feature)
200
+ 3. Commit your changes (git commit -am 'Add some feature')
201
+ 4. Push branch to remote (git push origin my-new-feature)
202
+ 5. Make a Pull Request
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ValidatesStructure'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,206 @@
1
+ require 'active_model'
2
+ require 'securerandom'
3
+
4
+ module ValidatesStructure
5
+
6
+ class Validator
7
+ include ActiveModel::Validations
8
+
9
+ class_attribute :keys, instance_writer: false
10
+ class_attribute :values, instance_writer: false
11
+ class_attribute :nested_validators, instance_writer: false
12
+
13
+ attr_accessor :keys
14
+
15
+ def initialize(hash)
16
+ self.class.initialize_class_attributes
17
+ self.keys = []
18
+
19
+ return unless hash.is_a?(Hash)
20
+
21
+ hash.each do |key, value|
22
+ self.keys << key.to_s
23
+ send "#{key}=", value if respond_to? "#{key}="
24
+ end
25
+ end
26
+
27
+ def self.key(key, klass, validations={}, &block)
28
+ initialize_class_attributes
29
+ key = key.to_s
30
+
31
+ if klass.class != Class
32
+ raise ArgumentError.new("Types must be given as classes")
33
+ end
34
+ unless @nested_array_key.nil?
35
+ raise ArgumentError.new("Key can not only appear within a block of a key of type Array")
36
+ end
37
+ if keys.include?(key)
38
+ raise ArgumentError.new("Dublicate key \"#{key}\"")
39
+ end
40
+
41
+ keys << key
42
+ attr_accessor key
43
+
44
+ validations = prepare_validations(klass, validations, &block)
45
+ validates key, validations
46
+
47
+ nest_dsl(klass, key, &block)
48
+ end
49
+
50
+ def self.value(klass, validations={}, &block)
51
+ initialize_class_attributes
52
+
53
+ if klass.class != Class
54
+ raise ArgumentError.new("Types must be given as classes")
55
+ end
56
+ if @nested_array_key.nil?
57
+ raise ArgumentError.new("Value can only appear within the block of a key of Array type")
58
+ end
59
+ if values[@nested_array_key]
60
+ raise ArgumentError.new("Value can only appear once within a block")
61
+ end
62
+ values[@nested_array_key] = klass
63
+
64
+ validations = prepare_validations(klass, validations, &block)
65
+ validates @nested_array_key, enumerable: validations
66
+ nest_dsl(klass, @nested_array_key, &block)
67
+ end
68
+
69
+ def self.human_attribute_name(attr, *)
70
+ "/#{attr}"
71
+ end
72
+
73
+ validate do
74
+ (keys - self.class.keys).each do |key|
75
+ errors.add(key, "is not a known key")
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ def self.initialize_class_attributes
82
+ self.keys ||= []
83
+ self.values ||= {}
84
+ self.nested_validators ||= {}
85
+ end
86
+
87
+ def self.prepare_validations(klass, validations, &block)
88
+ validations = validations.dup
89
+ validations[:klass] = { klass: (klass.ancestors.include?(Validator) ? Hash : klass) }
90
+ unless validations[:allow_nil] == true || validations[:allow_blank] == true
91
+ if klass == String
92
+ validations[:not_blank] = true
93
+ else
94
+ validations[:not_nil] = true
95
+ end
96
+ end
97
+ validations[:nested] = true if block_given? || klass.ancestors.include?(Validator)
98
+ validations
99
+ end
100
+
101
+ def self.nest_dsl(klass, key, &block)
102
+ if klass.ancestors.include?(Validator)
103
+ self.nested_validators[key] = klass
104
+ elsif block_given?
105
+ case
106
+ when klass == Hash
107
+ klass_name = "Annonimous#{ key.to_s.camelize }Validator#{SecureRandom.uuid.tr('-','')}"
108
+ const_set(klass_name, Class.new(self.superclass))
109
+ validator = const_get(klass_name)
110
+ validator.instance_eval(&block)
111
+ self.nested_validators[key] = validator
112
+ when klass == Array
113
+ @nested_array_key = key
114
+ yield
115
+ @nested_array_key = nil
116
+ else
117
+ raise ArgumentError.new("Didn't expect a block for the type \"#{klass}\"")
118
+ end
119
+ end
120
+ end
121
+
122
+ def self.model_name
123
+ ActiveModel::Name.new(self, nil, "temp")
124
+ end
125
+
126
+ class Boolean
127
+ end
128
+
129
+ class KlassValidator < ActiveModel::EachValidator
130
+ def validate_each(record, attribute, value)
131
+ return if value.nil?
132
+ klass = options[:klass]
133
+ if klass == Boolean
134
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
135
+ record.errors.add attribute, "has class \"#{value.class}\" but should be boolean"
136
+ end
137
+ elsif !(value.is_a?(klass))
138
+ record.errors.add attribute, "has class \"#{value.class}\" but should be a \"#{klass}\""
139
+ end
140
+ end
141
+ end
142
+
143
+ class NotNilValidator < ActiveModel::EachValidator
144
+ def validate_each(record, attribute, value)
145
+ if value.nil?
146
+ record.errors.add attribute, "must not be nil"
147
+ end
148
+ end
149
+ end
150
+
151
+ class NotBlankValidator < ActiveModel::EachValidator
152
+ def validate_each(record, attribute, value)
153
+ if value.blank?
154
+ record.errors.add attribute, "must not be empty"
155
+ end
156
+ end
157
+ end
158
+
159
+ class NestedValidator < ActiveModel::EachValidator
160
+ def validate_each(record, attribute, value)
161
+ return unless value.is_a?(Hash)
162
+ return if record.errors.keys.map(&:to_s).include?(attribute)
163
+
164
+ validator = record.class.nested_validators[attribute]
165
+ return if validator.nil?
166
+
167
+ nested = validator.new(value)
168
+ nested.valid?
169
+ nested.errors.each do |nested_attribute, message|
170
+ record.errors.add("#{attribute}/#{nested_attribute}", message)
171
+ end
172
+ end
173
+ end
174
+
175
+ class EnumerableValidator < ActiveModel::EachValidator
176
+ # Validates each value in an enumerable class using ActiveModel validations.
177
+ # Adapted from a snippet by Milovan Zogovic (http://stackoverflow.com/a/12744945)
178
+ def validate_each(record, attribute, values)
179
+ return unless values.respond_to?(:each_with_index)
180
+ values.each_with_index do |value, index|
181
+ options.each do |key, args|
182
+ validator_options = { attributes: attribute }
183
+ validator_options.merge!(args) if args.is_a?(Hash)
184
+
185
+ next if value.nil? && validator_options[:allow_nil]
186
+ next if value.blank? && validator_options[:allow_blank]
187
+ next if key.to_s == "allow_nil"
188
+ next if key.to_s == "allow_blank"
189
+
190
+ validator_class_name = "#{key.to_s.camelize}Validator"
191
+ validator_class = self.class.parent.const_get(validator_class_name)
192
+ validator = validator_class.new(validator_options)
193
+
194
+ # TODO: There should be a better way!
195
+ tmp_record = record.dup
196
+ validator.validate_each(tmp_record, attribute, value)
197
+ tmp_record.errors.each do |nested_attribute, error|
198
+ indexed_attribute = nested_attribute.to_s.sub(/^#{attribute}/, "#{attribute}[#{index}]")
199
+ record.errors.add(indexed_attribute, error)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,3 @@
1
+ module ValidatesStructure
2
+ VERSION = "0.2.0"
3
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: validates-structure
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Magnus Rex
8
+ - Daniel Ström
9
+ - Jean-Louis Giordano
10
+ - Patrik Kårlin
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2014-07-11 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activemodel
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 4.1.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.1.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ - !ruby/object:Gem::Dependency
45
+ name: debugger
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ - !ruby/object:Gem::Dependency
73
+ name: guard-rspec
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ description: Uses the power and familiarity of ActiveModel::Validations to validate
87
+ hash structures. Designed for detecting and providing feedback on bad requests to
88
+ your RESTful Web Service.
89
+ email:
90
+ - dev@pugglepay.com
91
+ executables: []
92
+ extensions: []
93
+ extra_rdoc_files: []
94
+ files:
95
+ - lib/validates-structure/version.rb
96
+ - lib/validates-structure.rb
97
+ - LICENSE.txt
98
+ - Rakefile
99
+ - README.md
100
+ homepage: https://github.com/PugglePay/validates-structure
101
+ licenses: []
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.0.3
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: ActiveModel validations for nested structures like params.
123
+ test_files: []
124
+ has_rdoc: