active_model_type_validator 1.0.0

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