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