validated_object 1.0.0 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 917e06893558682ef44f4ae1570d73bb265dc980
4
- data.tar.gz: b622a865760b6b3626ed1f270313ef26686371f6
2
+ SHA256:
3
+ metadata.gz: 0c1aef88260ebc19d28fa8bd90a400ec6d05c45a60fea1632ebb1b81bbe2b456
4
+ data.tar.gz: 239216afd24adc90bb9ae4641253443a91d303e312f49b7bc6f348c541563f5f
5
5
  SHA512:
6
- metadata.gz: 95affa10a6d3ee9efc02ced04778a8df4adab2e03a914086afbe79717c679454766be1aae77d2aaa7f7da670b82ed5baf9dc20bf0a0b6c6b0c0527123ab2c732
7
- data.tar.gz: 079acd77f94c7c4664534a489fe5525ad47ab38883e666ea8575065138ce158c6d4b73a42d14b9ed175400e8e8251db2a44ff3b11811fb45229444fe6712d64d
6
+ metadata.gz: 032c79475b1a99e990f4b04da2cda94cd5cca556cb38e385a783300ef675bfcd2ed51cc055d653b86f77b70270de87518cd7296b1c754a108f11c70d4e989c86
7
+ data.tar.gz: affdd5eb1d8dd2525c6b0fd7730fb907317ce0ad3bf8f105b42d67c66aba69e3389c65f7eae85dbfc67ed273e242761a34c67210153f4d9cbaaa694a3e6cac57
@@ -1,5 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1
4
3
  - 2.2.2
4
+ - 2.3.1
5
5
  before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,2 @@
1
+ # 1.1.0
2
+ Introduces the new Boolean pseudo-type.
data/README.md CHANGED
@@ -1,28 +1,18 @@
1
- [![Gem Version](https://badge.fury.io/rb/validated_object.svg)](https://badge.fury.io/rb/validated_object) [![Build Status](https://travis-ci.org/dogweather/validated_object.svg?branch=master)](https://travis-ci.org/dogweather/validated_object) [![Code Climate](https://codeclimate.com/github/dogweather/validated_object/badges/gpa.svg)](https://codeclimate.com/github/dogweather/validated_object)
1
+ [![Gem Version](https://badge.fury.io/rb/validated_object.svg)](https://badge.fury.io/rb/validated_object) [![Build Status](https://travis-ci.org/public-law/validated_object.svg?branch=master)](https://travis-ci.org/public-law/validated_object) [![Code Climate](https://codeclimate.com/github/dogweather/validated_object/badges/gpa.svg)](https://codeclimate.com/github/dogweather/validated_object)
2
2
 
3
3
  # ValidatedObject
4
4
 
5
- Uses
6
- [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates)
7
- to create self-validating Plain Old Ruby objects. I wrote it for helping with CSV data imports into my Rails apps.
8
- Very readable error messages are also important in that context, to track down parsing errors. This gem provides those too.
5
+ Plain Old Ruby Objects + Rails Validations = **self-checking Ruby objects**.
9
6
 
10
7
 
11
- ## Installation
8
+ ## Goals
12
9
 
13
- Add this line to your application's Gemfile:
10
+ * Very readable error messages
11
+ * Clean, minimal syntax
14
12
 
15
- ```ruby
16
- gem 'validated_object'
17
- ```
13
+ This is a small layer around
14
+ [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates). (About 18 lines of code.) So if you know how to use Rails Validations, you're good to go. I wrote this to help with CSV data imports and [website microdata generation](https://github.com/dogweather/schema-dot-org).
18
15
 
19
- And then execute:
20
-
21
- $ bundle
22
-
23
- Or install it yourself as:
24
-
25
- $ gem install validated_object
26
16
 
27
17
  ## Usage
28
18
 
@@ -33,47 +23,120 @@ All of the [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveM
33
23
 
34
24
  ```ruby
35
25
  class Dog < ValidatedObject::Base
36
- attr_accessor :name, :birthday
26
+ # Plain old Ruby
27
+ attr_accessor :name, :birthday # attr_reader is supported as well for read-only attributes
37
28
 
29
+ # Plain old Rails
38
30
  validates :name, presence: true
39
- validates :birthday, type: Date, allow_nil: true
31
+
32
+ # A new type-validation if you'd like to use it
33
+ validates :birthday, type: Date, allow_nil: true # Strongly typed but optional
40
34
  end
41
35
  ```
42
36
 
37
+ The included `TypeValidator` is what enables `type: Date`, above. All classes can be checked, as well as a pseudo-class `Boolean`. E.g.:
38
+
39
+ ```ruby
40
+ #...
41
+ validates :premium_membership, type: Boolean
42
+ #...
43
+ ```
44
+
43
45
  ### Instantiating and automatically validating
44
46
 
45
47
  ```ruby
46
- # The dog1 instance validates itself at the end of instantiation.
47
- # Here, it succeeds and so doesn't raise an exception.
48
- dog1 = Dog.new do |d|
49
- d.name = 'Spot'
50
- end
48
+ # This Dog instance validates itself at the end of instantiation.
49
+ spot = Dog.new(name: 'Spot')
50
+ ```
51
51
 
52
- # We can also explicitly test for validity
53
- dog1.valid? # => true
52
+ ```ruby
53
+ # We can also explicitly test for validity because all of
54
+ # ActiveModel::Validations is available.
55
+ spot.valid? # => true
54
56
 
55
- dog1.birthday = Date.new(2015, 1, 23)
56
- dog1.valid? # => true
57
+ spot.birthday = Date.new(2015, 1, 23)
58
+ spot.valid? # => true
57
59
  ```
58
60
 
59
- ### Making an instance _invalid_
61
+ ### Good error messages
62
+
63
+ Any of the standard Validations methods can be
64
+ used to test an instance, plus the custom `check_validations!` convenience method:
60
65
 
61
66
  ```ruby
62
- dog1.birthday = '2015-01-23'
63
- dog1.valid? # => false
64
- dog1.check_validations! # => ArgumentError: Birthday is class String, not Date
67
+ spot.birthday = '2015-01-23'
68
+ spot.valid? # => false
69
+ spot.check_validations! # => ArgumentError: Birthday is a String, not a Date
65
70
  ```
66
71
 
72
+ Note the clear, explicit error message. These are great when reading a log
73
+ file following a data import. It describes all the invalid conditions. Let's
74
+ test it by making another attribute invalid:
75
+
76
+ ```ruby
77
+ spot.name = nil
78
+ spot.check_validations! # => ArgumentError: Name can't be blank; Birthday is a String, not a Date
79
+ ```
80
+
81
+
82
+ ### Use in parsing data
83
+
84
+ I often use a validated object in a loop to import data, e.g.:
85
+
86
+ ```ruby
87
+ # Import a CSV file of dogs
88
+ dogs = []
89
+ csv.next_row do |row|
90
+ begin
91
+ dogs << Dog.new(name: row.name)
92
+ rescue ArgumentError => e
93
+ logger.warn(e)
94
+ end
95
+ end
96
+ ```
97
+
98
+ The result is that `dogs` is an array of guaranteed valid Dog objects. And the
99
+ error log lists unparseable rows with good info for tracking down problems in
100
+ the data.
101
+
102
+ ### Use in code generation
103
+
104
+ My [Schema.org microdata generation gem](https://github.com/dogweather/schema-dot-org) uses ValidatedObjects to recursively create well formed HTML / JSON-LD.
105
+
106
+ ## Installation
107
+
108
+ Add this line to your application's Gemfile:
109
+
110
+ ```ruby
111
+ gem 'validated_object'
112
+ ```
113
+
114
+ And then execute:
115
+
116
+ $ bundle
117
+
118
+ Or install it yourself as:
119
+
120
+ $ gem install validated_object
121
+
122
+
67
123
 
68
124
  ## Development
69
125
 
70
- (TODO: Verify these instructions.) After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
126
+ (TODO: Verify these instructions.) After checking out the repo, run `bin/setup`
127
+ to install dependencies. Then, run `rake spec` to run the tests. You can also
128
+ run `bin/console` for an interactive prompt that will allow you to experiment.
71
129
 
72
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
130
+ To install this gem onto your local machine, run `bundle exec rake install`. To
131
+ release a new version, update the version number in `version.rb`, and then run
132
+ `bundle exec rake release`, which will create a git tag for the version, push
133
+ git commits and tags, and push the `.gem` file to
134
+ [rubygems.org](https://rubygems.org).
73
135
 
74
136
  ## Contributing
75
137
 
76
- Bug reports and pull requests are welcome on GitHub at https://github.com/dogweather/validated_object.
138
+ Bug reports and pull requests are welcome on GitHub at
139
+ https://github.com/dogweather/validated_object.
77
140
 
78
141
 
79
142
  ## License
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
2
4
  require 'validated_object/version'
3
5
 
@@ -5,7 +7,7 @@ module ValidatedObject
5
7
  # @abstract Subclass and add `attr_accessor` and validations
6
8
  # to create custom validating objects.
7
9
  #
8
- # Uses [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates)
10
+ # Uses {http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates ActiveModel::Validations}
9
11
  # to create self-validating Plain Old Ruby objects. This is especially
10
12
  # useful when importing data from one system into another. This class also
11
13
  # creates very readable error messages.
@@ -21,9 +23,7 @@ module ValidatedObject
21
23
  # @example Instantiating and automatically validating
22
24
  # # The dog1 instance validates itself at the end of instantiation.
23
25
  # # Here, it succeeds and so doesn't raise an exception.
24
- # dog1 = Dog.new do |d|
25
- # d.name = 'Spot'
26
- # end
26
+ # dog1 = Dog.new name: 'Spot'
27
27
  #
28
28
  # # We can also explicitly test for validity
29
29
  # dog1.valid? # => true
@@ -42,44 +42,93 @@ module ValidatedObject
42
42
  class Base
43
43
  include ActiveModel::Validations
44
44
 
45
- # Instantiate and validate a new object.
45
+ EMPTY_HASH = {}.freeze
46
+
47
+ # A private class definition, not intended to
48
+ # be used directly. Implements a pseudo-boolean class
49
+ # enabling validations like this:
46
50
  #
47
- # @yieldparam [ValidatedObject] new_object the yielded new object
48
- # for configuration.
51
+ # validates :enabled, type: Boolean
52
+ class Boolean
53
+ end
54
+
55
+ # Instantiate and validate a new object.
56
+ # @example
57
+ # maru = Dog.new(birthday: Date.today, name: 'Maru')
49
58
  #
50
59
  # @raise [ArgumentError] if the object is not valid at the
51
- # end of initialization.
52
- def initialize
53
- yield(self)
60
+ # end of initialization or `attributes` is not a Hash.
61
+ def initialize(attributes=EMPTY_HASH)
62
+ raise ArgumentError, "#{attributes} is not a Hash" unless attributes.is_a?(Hash)
63
+
64
+ set_instance_variables from_hash: attributes
54
65
  check_validations!
55
- self
66
+ return self
56
67
  end
57
68
 
58
69
  # Run any validations and raise an error if invalid.
70
+ #
59
71
  # @raise [ArgumentError] if any validations fail.
72
+ # @return [ValidatedObject::Base] the receiver
60
73
  def check_validations!
61
74
  raise ArgumentError, errors.full_messages.join('; ') if invalid?
62
75
  self
63
76
  end
64
77
 
65
78
  # A custom validator which ensures an object is an instance of a class
66
- # or a subclass.
67
- # It's here as a nested class in {ValidatedObject} for easy
68
- # access by subclasses of {ValidatedObject::Base}.
79
+ # or a subclass. It supports a pseudo-boolean class for convenient
80
+ # validation. (Ruby doesn't have a built-in Boolean.)
81
+ #
82
+ # Automatically used in a `type` validation:
69
83
  #
70
84
  # @example Ensure that weight is a number
71
85
  # class Dog < ValidatedObject::Base
72
- # attr_accessor :weight
73
- # validates :weight, type: Numeric
86
+ # attr_accessor :weight, :neutered
87
+ # validates :weight, type: Numeric # Typed and required
88
+ # validates :neutered, type: Boolean, allow_nil: true # Typed but optional
74
89
  # end
75
90
  class TypeValidator < ActiveModel::EachValidator
76
91
  # @return [nil]
77
92
  def validate_each(record, attribute, value)
78
- expected = options[:with]
79
- return if value.kind_of?(expected)
93
+ expected_class = options[:with]
94
+
95
+ return if pseudo_boolean?(expected_class, value) ||
96
+ expected_class?(expected_class, value)
97
+
98
+ save_error(record, attribute, value, options)
99
+ end
100
+
101
+
102
+ private
103
+
104
+ def pseudo_boolean?(expected_class, value)
105
+ expected_class == Boolean && boolean?(value)
106
+ end
107
+
108
+ def expected_class?(expected_class, value)
109
+ value.is_a?(expected_class)
110
+ end
111
+
112
+ def boolean?(value)
113
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
114
+ end
115
+
116
+ def save_error(record, attribute, value, options)
117
+ record.errors.add attribute,
118
+ options[:message] || "is a #{value.class}, not a #{options[:with]}"
119
+ end
120
+ end
121
+
122
+
123
+ private
124
+
125
+ def set_instance_variables(from_hash:)
126
+ from_hash.each do |variable_name, variable_value|
127
+ # Test for the attribute reader
128
+ self.send variable_name.to_sym
80
129
 
81
- msg = options[:message] || "is class #{value.class}, not #{expected}"
82
- record.errors.add attribute, msg
130
+ # Set value in a way that succeeds even if attr is read-only
131
+ self.instance_variable_set "@#{variable_name}".to_sym, variable_value
83
132
  end
84
133
  end
85
134
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ValidatedObject
2
- VERSION = '1.0.0'.freeze
4
+ VERSION = '2.0.3'
3
5
  end
@@ -0,0 +1,17 @@
1
+ require 'date'
2
+ require 'validated_object'
3
+
4
+ class Dog < ValidatedObject::Base
5
+ attr_reader :name, :birthday
6
+ validates :name, presence: true
7
+ validates :birthday, type: Date, allow_nil: true
8
+ end
9
+
10
+ phoebe = Dog.new(name: 'Phoebe')
11
+ puts phoebe.inspect
12
+
13
+ maru = Dog.new(birthday: Date.today, name: 'Maru')
14
+ puts maru.inspect
15
+
16
+ hiro = Dog.new(birthday: 'today')
17
+ puts hiro.inspect
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'validated_object/version'
5
6
 
@@ -7,11 +8,11 @@ Gem::Specification.new do |spec|
7
8
  spec.name = 'validated_object'
8
9
  spec.version = ValidatedObject::VERSION
9
10
  spec.authors = ['Robb Shecter']
10
- spec.email = ['robb@weblaws.org']
11
+ spec.email = ['robb@public.law']
11
12
 
12
13
  spec.summary = 'Self-validating plain Ruby objects.'
13
14
  spec.description = 'A small wrapper around ActiveModel Validations.'
14
- spec.homepage = 'https://github.com/dogweather/validated_object'
15
+ spec.homepage = 'https://github.com/public-law/validated_object'
15
16
  spec.license = 'MIT'
16
17
 
17
18
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
@@ -27,8 +28,7 @@ Gem::Specification.new do |spec|
27
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
29
  spec.require_paths = ['lib']
29
30
 
30
- spec.add_development_dependency 'bundler', '~> 1.10'
31
- spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rake', '>= 12.3.3'
32
32
  spec.add_development_dependency 'rspec'
33
33
 
34
34
  spec.add_runtime_dependency 'activemodel', '>= 3.2.21'
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: validated_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robb Shecter
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-25 00:00:00.000000000 Z
11
+ date: 2020-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.10'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.10'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - "~>"
17
+ - - ">="
32
18
  - !ruby/object:Gem::Version
33
- version: '10.0'
19
+ version: 12.3.3
34
20
  type: :development
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - "~>"
24
+ - - ">="
39
25
  - !ruby/object:Gem::Version
40
- version: '10.0'
26
+ version: 12.3.3
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rspec
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -68,7 +54,7 @@ dependencies:
68
54
  version: 3.2.21
69
55
  description: A small wrapper around ActiveModel Validations.
70
56
  email:
71
- - robb@weblaws.org
57
+ - robb@public.law
72
58
  executables: []
73
59
  extensions: []
74
60
  extra_rdoc_files: []
@@ -77,6 +63,7 @@ files:
77
63
  - ".rspec"
78
64
  - ".travis.yml"
79
65
  - Gemfile
66
+ - HISTORY.md
80
67
  - LICENSE.txt
81
68
  - README.md
82
69
  - Rakefile
@@ -84,13 +71,14 @@ files:
84
71
  - bin/setup
85
72
  - lib/validated_object.rb
86
73
  - lib/validated_object/version.rb
74
+ - script/demo.rb
87
75
  - validated_object.gemspec
88
- homepage: https://github.com/dogweather/validated_object
76
+ homepage: https://github.com/public-law/validated_object
89
77
  licenses:
90
78
  - MIT
91
79
  metadata:
92
80
  allowed_push_host: https://rubygems.org
93
- post_install_message:
81
+ post_install_message:
94
82
  rdoc_options: []
95
83
  require_paths:
96
84
  - lib
@@ -105,10 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
93
  - !ruby/object:Gem::Version
106
94
  version: '0'
107
95
  requirements: []
108
- rubyforge_project:
109
- rubygems_version: 2.5.0
110
- signing_key:
96
+ rubygems_version: 3.1.4
97
+ signing_key:
111
98
  specification_version: 4
112
99
  summary: Self-validating plain Ruby objects.
113
100
  test_files: []
114
- has_rdoc: