validated_object 1.0.0 → 2.0.3
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 +5 -5
- data/.travis.yml +1 -1
- data/HISTORY.md +2 -0
- data/README.md +98 -35
- data/lib/validated_object.rb +69 -20
- data/lib/validated_object/version.rb +3 -1
- data/script/demo.rb +17 -0
- data/validated_object.gemspec +6 -6
- metadata +14 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0c1aef88260ebc19d28fa8bd90a400ec6d05c45a60fea1632ebb1b81bbe2b456
|
4
|
+
data.tar.gz: 239216afd24adc90bb9ae4641253443a91d303e312f49b7bc6f348c541563f5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 032c79475b1a99e990f4b04da2cda94cd5cca556cb38e385a783300ef675bfcd2ed51cc055d653b86f77b70270de87518cd7296b1c754a108f11c70d4e989c86
|
7
|
+
data.tar.gz: affdd5eb1d8dd2525c6b0fd7730fb907317ce0ad3bf8f105b42d67c66aba69e3389c65f7eae85dbfc67ed273e242761a34c67210153f4d9cbaaa694a3e6cac57
|
data/.travis.yml
CHANGED
data/HISTORY.md
ADDED
data/README.md
CHANGED
@@ -1,28 +1,18 @@
|
|
1
|
-
[](https://badge.fury.io/rb/validated_object) [](https://badge.fury.io/rb/validated_object) [](https://travis-ci.org/public-law/validated_object) [](https://codeclimate.com/github/dogweather/validated_object)
|
2
2
|
|
3
3
|
# ValidatedObject
|
4
4
|
|
5
|
-
|
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
|
-
##
|
8
|
+
## Goals
|
12
9
|
|
13
|
-
|
10
|
+
* Very readable error messages
|
11
|
+
* Clean, minimal syntax
|
14
12
|
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
47
|
-
|
48
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
57
|
+
spot.birthday = Date.new(2015, 1, 23)
|
58
|
+
spot.valid? # => true
|
57
59
|
```
|
58
60
|
|
59
|
-
###
|
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
|
-
|
63
|
-
|
64
|
-
|
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`
|
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
|
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
|
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
|
data/lib/validated_object.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
#
|
48
|
-
|
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
|
-
|
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
|
-
#
|
68
|
-
#
|
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
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
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
|
data/script/demo.rb
ADDED
@@ -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
|
data/validated_object.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
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@
|
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/
|
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 '
|
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:
|
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:
|
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:
|
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:
|
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@
|
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/
|
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
|
-
|
109
|
-
|
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:
|