active_model_type_validator 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +3 -0
  3. data/Appraisals +3 -0
  4. data/CHANGELOG.md +3 -0
  5. data/LICENSE.md +22 -0
  6. data/README.md +74 -0
  7. data/Rakefile +8 -0
  8. data/active_model_type_validator.gemspec +37 -0
  9. data/gemfiles/rails_4.2.gemfile +7 -0
  10. data/lib/active_model_type_validator.rb +3 -0
  11. data/lib/active_model_type_validator/contents.rb +30 -0
  12. data/lib/active_model_type_validator/locale/en.yml +4 -0
  13. data/lib/active_model_type_validator/object_type.rb +99 -0
  14. data/lib/active_model_type_validator/version.rb +7 -0
  15. data/test/active_model_type_validator/active_model_type_validator_test.rb +11 -0
  16. data/test/active_model_type_validator/contents_test.rb +129 -0
  17. data/test/active_model_type_validator/object_type_test.rb +201 -0
  18. data/test/app/rails_4.2/Rakefile +6 -0
  19. data/test/app/rails_4.2/app/assets/javascripts/application.js +16 -0
  20. data/test/app/rails_4.2/app/assets/stylesheets/application.css +15 -0
  21. data/test/app/rails_4.2/app/controllers/application_controller.rb +5 -0
  22. data/test/app/rails_4.2/app/helpers/application_helper.rb +2 -0
  23. data/test/app/rails_4.2/app/views/layouts/application.html.erb +14 -0
  24. data/test/app/rails_4.2/bin/bundle +3 -0
  25. data/test/app/rails_4.2/bin/rails +8 -0
  26. data/test/app/rails_4.2/bin/rake +8 -0
  27. data/test/app/rails_4.2/bin/setup +29 -0
  28. data/test/app/rails_4.2/bin/spring +15 -0
  29. data/test/app/rails_4.2/config.ru +4 -0
  30. data/test/app/rails_4.2/config/application.rb +26 -0
  31. data/test/app/rails_4.2/config/boot.rb +3 -0
  32. data/test/app/rails_4.2/config/database.yml +25 -0
  33. data/test/app/rails_4.2/config/environment.rb +5 -0
  34. data/test/app/rails_4.2/config/environments/development.rb +41 -0
  35. data/test/app/rails_4.2/config/environments/production.rb +79 -0
  36. data/test/app/rails_4.2/config/environments/test.rb +42 -0
  37. data/test/app/rails_4.2/config/initializers/assets.rb +11 -0
  38. data/test/app/rails_4.2/config/initializers/backtrace_silencers.rb +7 -0
  39. data/test/app/rails_4.2/config/initializers/cookies_serializer.rb +3 -0
  40. data/test/app/rails_4.2/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/test/app/rails_4.2/config/initializers/inflections.rb +16 -0
  42. data/test/app/rails_4.2/config/initializers/mime_types.rb +4 -0
  43. data/test/app/rails_4.2/config/initializers/session_store.rb +3 -0
  44. data/test/app/rails_4.2/config/initializers/wrap_parameters.rb +14 -0
  45. data/test/app/rails_4.2/config/locales/en.yml +23 -0
  46. data/test/app/rails_4.2/config/routes.rb +56 -0
  47. data/test/app/rails_4.2/config/secrets.yml +22 -0
  48. data/test/app/rails_4.2/db/schema.rb +0 -0
  49. data/test/app/rails_4.2/db/seeds.rb +7 -0
  50. data/test/app/rails_4.2/public/404.html +67 -0
  51. data/test/app/rails_4.2/public/422.html +67 -0
  52. data/test/app/rails_4.2/public/500.html +66 -0
  53. data/test/app/rails_4.2/public/favicon.ico +0 -0
  54. data/test/app/rails_4.2/public/robots.txt +5 -0
  55. data/test/test_helper.rb +20 -0
  56. metadata +260 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 72c8544aafca46e9293835f295a5a8e87013d4c6
4
+ data.tar.gz: 9f9b086ba8474d5ed70057371c69f538455ea17a
5
+ SHA512:
6
+ metadata.gz: b2769e445e21187b1108d9863d5cbbb2dd78996eb396926c14397072d97ff090811aa0f40422365addac2a41ca732fbd319c01eaecc1dcb0c2815e3192f93713
7
+ data.tar.gz: ae548970359df5bc19f1ab603935d5e50ff139f19b4d0950fabf76d3733fd819410ce0f7f591bf0d0012923dae3e262ad0501f8a3d2c16199e2094cafada4da8
@@ -0,0 +1,3 @@
1
+ -
2
+ LICENSE.md
3
+ CHANGELOG.md
@@ -0,0 +1,3 @@
1
+ appraise "rails-4.2" do
2
+ gem "rails", "4.2.4"
3
+ end
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ * Initial development version
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Todd Knarr
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ # ActiveModelTypeValidator
2
+
3
+ Provides two ActiveModel validators:
4
+
5
+ 1. One to validate that an attribute is of the correct type. Multiple types are allowed.
6
+ 2. One to validate the contents of an attribute by calling `#valid?` on it, performing
7
+ the same job as ActiveRecord's `validates_associated`.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'active_model_type_validator'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install active_model_type_validator
24
+
25
+ ## Usage
26
+
27
+ The usage is fairly standard. The `object_type` validator takes one option `:type` which
28
+ is either a type name (string, symbol or type) or an array of type names. The `contents`
29
+ validator needs no options. Both take the standard options all validators do, except
30
+ that `object_type` ignores the `:allow_blank` option (you should use `:allow_nil` if you
31
+ need equivalent behavior).
32
+
33
+ Examples:
34
+
35
+ ```ruby
36
+ class MyClass
37
+ include ActiveModel::Model
38
+ include ActiveModel::Validations
39
+
40
+ validates :string_field, object_type: { type: String }
41
+ validates :integer_field, object_type: { type: Integer }
42
+ validates :numeric_field, object_type: { type: [Integer, Float] }
43
+ validates :object_field, contents: true, object_type: { type: ChildClass }
44
+
45
+ attr_accessor :string_field
46
+ attr_accessor :integer_field
47
+ attr_accessor :numeric_field
48
+ attr_accessor :object_field
49
+
50
+ def initialize
51
+ @string_field = 'my string'
52
+ @integer_field = 42
53
+ @numeric_field = 7
54
+ @object_field = ChildClass.new
55
+ end
56
+
57
+ end
58
+ ```
59
+
60
+ The `contents` validator adds what's equivalent to ActiveModel's `associated` validator,
61
+ passing if calling `#valid?` on the attribute returns true.
62
+
63
+ The `object_type` validator lets you test ActiveModel objects used in an API to make sure
64
+ they contain objects of the expected types rather than something you aren't expecting. It
65
+ fills in a gap I was annoyed by when I validate incoming API objects immediately upon
66
+ receiving them and go no further if they fail validation.
67
+
68
+ ## Contributing
69
+
70
+ 1. Fork it ( https://github.com/tknarr/active_model_type_validator/fork )
71
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
73
+ 4. Push to the branch (`git push origin my-new-feature`)
74
+ 5. Create a new Pull Request
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/gem_tasks'
3
+ require 'yard'
4
+
5
+ YARD::Rake::YardocTask.new do |t|
6
+ t.options += ['--title', "Active Model Type Validator #{ActiveModelTypeValidator::VERSION} Documentation"]
7
+ end
8
+
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'active_model_type_validator/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "active_model_type_validator"
9
+ spec.version = ActiveModelTypeValidator::VERSION
10
+ spec.date = Time.now.strftime('%Y-%m-%d')
11
+ spec.author = 'Todd Knarr'
12
+ spec.email = 'tknarr@silverglass.org'
13
+ spec.summary = %q{ActiveModel validators to validate object types and contained/associated objects.}
14
+ spec.description = %q{ActiveModel validators that validate the type of attributes, and that do recursive validation of contained objects parallel to ActiveRecord's validates_associated.}
15
+ spec.homepage = 'https://github.com/tknarr/active_model_type_validator'
16
+ spec.license = "MIT"
17
+
18
+ spec.files = Dir.glob('lib/**/*').select { |path| File.file?(path) } +
19
+ [ __FILE__ ]
20
+ spec.test_files = Dir.glob('test/**/*').select { |path| File.file?(path) && !File.fnmatch('*.{log,sqlite3}', path, File::FNM_EXTGLOB) } +
21
+ Dir.glob('gemfiles/*.gemfile') + [ 'Rakefile', 'Appraisals' ]
22
+ spec.extra_rdoc_files = [ 'README.md', 'CHANGELOG.md', 'LICENSE.md', '.yardopts' ]
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'rails', '~> 4.2'
26
+ spec.add_dependency 'activemodel', '~> 4.2'
27
+
28
+ # Development
29
+ spec.add_development_dependency 'bundler', '~> 1.7'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'yard', '~> 0'
32
+
33
+ # Testing
34
+ spec.add_development_dependency 'minitest', '~> 5'
35
+ spec.add_development_dependency 'appraisal', '~> 2'
36
+ spec.add_development_dependency 'sqlite3', '~> 1'
37
+ end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2.4"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,3 @@
1
+ require 'active_model_type_validator/version'
2
+ require 'active_model_type_validator/object_type'
3
+ require 'active_model_type_validator/contents'
@@ -0,0 +1,30 @@
1
+ module ActiveModel
2
+
3
+ module Validations
4
+
5
+ # Implements the mechanics of <tt>validates_contents_of</tt>.
6
+ class ContentsValidator < ActiveModel::EachValidator
7
+
8
+ # Standard validation method for an <tt>EachValidator</tt>.
9
+ def validate_each(object, attribute, value)
10
+ if Array.wrap(value).reject { |r| r.valid? }.any?
11
+ object.errors.add(attribute, :invalid, options.merge(:value => value))
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ module HelperMethods
18
+
19
+ # An implementation of the associated validator from ActiveRecord.
20
+ # All options and behaviors are identical. It simply calls #valid? on each
21
+ # of the attributes listed and returns the logical AND of them all.
22
+ def validates_contents_of(*attr_names)
23
+ validates_with ContentsValidator, _merge_attributes(attr_names)
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ wrong_type: "cannot be of type %{type}"
@@ -0,0 +1,99 @@
1
+ module ActiveModel
2
+
3
+ module Validations
4
+
5
+ # Implements the mechanics of <tt>validates_object_type_of</tt>.
6
+ #
7
+ # The standard <tt>#initialize</tt> and <tt>#validate_each</tt> methods are implemented.
8
+ class ObjectTypeValidator < EachValidator
9
+
10
+ # Standard initializer for an <tt>EachValidator</tt>.
11
+ def initialize(options)
12
+ @types = []
13
+ # Remove :allow_blank since it's nonsensical when testing types
14
+ options.delete(:allow_blank)
15
+ # Set :allow_nil true if it's not present
16
+ t = options[:allow_nil]
17
+ options[:allow_nil] = true if t.nil?
18
+ # Filter out missing or blank type entries and convert others
19
+ # to class via constantize.
20
+ t = Array(options.delete(:type))
21
+ t.each do |te|
22
+ if te.is_a?(Class)
23
+ @types.append(te)
24
+ elsif te.is_a?(String)
25
+ begin
26
+ @types.append(te.constantize) unless te.blank?
27
+ rescue NameError
28
+ raise ArgumentError, "Type #{te} is invalid"
29
+ end
30
+ elsif te.is_a?(Symbol)
31
+ begin
32
+ @types.append(te.to_s.constantize)
33
+ rescue NameError
34
+ raise ArgumentError, "Type #{te.to_s} is invalid"
35
+ end
36
+ else
37
+ raise ArgumentError, "Type #{te.to_s} is a #{te.class.name} which is invalid"
38
+ end
39
+ end
40
+ raise ArgumentError, 'At least one type must be given' if @types.empty?
41
+ super(options)
42
+ check_validity!
43
+ end
44
+
45
+ # Standard validation method for an <tt>EachValidator</tt>.
46
+ def validate_each(record, attr_name, value)
47
+ errors_options = options.except(:type)
48
+ errors_options[:type] = value.class
49
+ right_type = false
50
+ @types.each do |t|
51
+ # Entries in @types are classes, not strings
52
+ right_type = true if value.is_a?(t)
53
+ end
54
+ record.errors.add(attr_name, (options[:message] || :wrong_type), errors_options) unless right_type
55
+ end
56
+
57
+ end
58
+
59
+ module HelperMethods
60
+
61
+ # Validates that the specified attributes are of a given type (as defined by <tt>#is_a?</tt>).
62
+ #
63
+ # class Person < ActiveRecord::Base
64
+ # validates_type_of :first_name, type: String
65
+ # end
66
+ #
67
+ # The +first_name+ attribute must be in the object and it must be a X.
68
+ #
69
+ # If you want to validate the type of a boolean field (where the real
70
+ # types are +TrueClass+ and +FalseClass+), you will want to use
71
+ # both those classes in the list of types and set +:allow_nil+ to false.
72
+ #
73
+ # By default this validator sets +:allow_nil+ to true to allow nil values to
74
+ # pass through validation even though +NilClass+ isn't explicitly listed, and
75
+ # you're expected to use a presence validator if you want the attribute to have
76
+ # to be present (non-nil). You can set +:allow_nil+ to false to cause even nil
77
+ # values to be checked against the type list and fail if NilClass isn't listed,
78
+ # mimicking the effect of a presence validator.
79
+ #
80
+ # Configuration options:
81
+ # * <tt>:type</tt> - An array of class names (or symbols or strings naming class
82
+ # names) which are legal for values of the attribute.
83
+ # * <tt>:message</tt> - A custom error message (default is: "cannot be of type X").
84
+ # Messages can find the offending type name for interpolation in +:type+.
85
+ #
86
+ # There is also a list of default options supported by every validator:
87
+ # +:if+, +:unless+, +:on+, +:allow_nil+, and +:strict+. +:allow_blank+ is
88
+ # not allowed because it's nonsensical and confusing when applied to type
89
+ # checking.
90
+ # See <tt>ActiveModel::Validation#validates</tt> for more information
91
+ def validates_object_type_of(*attr_names)
92
+ validates_with ObjectTypeValidator, _merge_attributes(attr_names)
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,7 @@
1
+ # Exists just for the version number
2
+ module ActiveModelTypeValidator
3
+
4
+ # Version number
5
+ VERSION = '1.0.0'
6
+
7
+ end
@@ -0,0 +1,11 @@
1
+ require '../test_helper'
2
+ require 'active_model_type_validator'
3
+
4
+ class ActiveModelTypeValidatorTest < ActiveSupport::TestCase
5
+
6
+ test 'API' do
7
+ assert_not_nil ActiveModelTypeValidator::VERSION
8
+ assert_kind_of String, ActiveModelTypeValidator::VERSION
9
+ end
10
+
11
+ end
@@ -0,0 +1,129 @@
1
+ require '../test_helper'
2
+ require 'active_model_type_validator'
3
+
4
+ class MyClass1
5
+ include ActiveModel::Model
6
+ include ActiveModel::Validations
7
+ validates :a1, presence: true, numericality: { only_integer: true, greater_than: 0 }
8
+ validates :b1, presence: true, numericality: { only_integer: true, greater_than: 0 }
9
+ validates :c1, presence: true, contents: true
10
+
11
+ attr_accessor :a1
12
+ attr_accessor :b1
13
+ attr_accessor :c1
14
+
15
+ end
16
+
17
+ class MyClass1a
18
+ include ActiveModel::Model
19
+ include ActiveModel::Validations
20
+ validates :a1, presence: true, numericality: { only_integer: true, greater_than: 0 }
21
+ validates :b1, presence: true, numericality: { only_integer: true, greater_than: 0 }
22
+ validates :c1, presence: true
23
+
24
+ attr_accessor :a1
25
+ attr_accessor :b1
26
+ attr_accessor :c1
27
+
28
+ end
29
+
30
+ class MyClass1b
31
+ include ActiveModel::Model
32
+ include ActiveModel::Validations
33
+ validates :a1, presence: true, numericality: { only_integer: true, greater_than: 0 }
34
+ validates :b1, presence: true, numericality: { only_integer: true, greater_than: 0 }
35
+ validates :c1, contents: { allow_nil: true }
36
+ validates :c2, contents: true
37
+
38
+ attr_accessor :a1
39
+ attr_accessor :b1
40
+ attr_accessor :c1
41
+ attr_accessor :c2
42
+
43
+ end
44
+
45
+ class MyClass2
46
+ include ActiveModel::Model
47
+ include ActiveModel::Validations
48
+ validates :a2, presence: true, numericality: { only_integer: true, greater_than: 0 }
49
+ validates :b2, presence: true, numericality: { only_integer: true, greater_than: 0 }
50
+
51
+ attr_accessor :a2
52
+ attr_accessor :b2
53
+
54
+ end
55
+
56
+ class RecursiveTest < ActiveSupport::TestCase
57
+
58
+ def setup
59
+ @model = MyClass1.new
60
+ @model.a1 = 57
61
+ @model.b1 = nil
62
+ @model.c1 = MyClass2.new
63
+ @model.c1.a2 = 99
64
+ @model.c1.b2 = nil
65
+
66
+ @not_validated = MyClass1a.new
67
+ @not_validated.a1 = 57
68
+ @not_validated.b1 = nil
69
+ @not_validated.c1 = MyClass2.new
70
+ @not_validated.c1.a2 = 99
71
+ @not_validated.c1.b2 = nil
72
+
73
+ @no_child = MyClass1b.new
74
+ @no_child.a1 = 57
75
+ @no_child.b1 = nil
76
+ @no_child.c1 = MyClass2.new
77
+ @no_child.c1.a2 = 99
78
+ @no_child.c1.b2 = nil
79
+ @no_child.c2 = MyClass2.new
80
+ @no_child.c2.a2 = 99
81
+ @no_child.c2.b2 = nil
82
+
83
+ end
84
+
85
+ test 'valid parent valid child' do
86
+ @model.b1 = 17
87
+ @model.c1.b2 = 23
88
+ assert @model.valid?
89
+ end
90
+
91
+ test 'valid parent invalid child' do
92
+ @model.b1 = 17
93
+ @model.c1.b2 = -23
94
+ assert_not @model.valid?
95
+ end
96
+
97
+ test 'invalid parent valid child' do
98
+ @model.b1 = -17
99
+ @model.c1.b2 = 23
100
+ assert_not @model.valid?
101
+ end
102
+
103
+ test 'invalid parent invalid child' do
104
+ @model.b1 = -17
105
+ @model.c1.b2 = -23
106
+ assert_not @model.valid?
107
+ end
108
+
109
+ test 'valid parent invalid child not validated' do
110
+ @not_validated.b1 = 17
111
+ @not_validated.c1.b2 = -23
112
+ assert @not_validated.valid?
113
+ end
114
+
115
+ test 'valid parent without child allow nil' do
116
+ @no_child.b1 = 17
117
+ @no_child.c1 = nil
118
+ @no_child.c2.b2 = 23
119
+ assert @no_child.valid?
120
+ end
121
+
122
+ test 'valid parent without child no allow nil' do
123
+ @no_child.b1 = 17
124
+ @no_child.c1.b2 = 23
125
+ @no_child.c2 = nil
126
+ assert_not @model.valid?
127
+ end
128
+
129
+ end