robert 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f0ca595b26c5a3f3e3b633cb9bee8bade4944d5c
4
+ data.tar.gz: e84ab645d934bb08b3acd16e4ca977b600faf29d
5
+ SHA512:
6
+ metadata.gz: 5318c53a44551c098e1b9ed069192a089f52a41af039b0e2d252c91d9a37854e00ecb00ffd0186b7a64da3af056c4e842b357d9bd1370b5ea5074ebe7b7bf3ac
7
+ data.tar.gz: b289fcee70b6410a98dd690d8c002f6097cd266b0041ff90acfade14a9c1b8e70236ecbb4b45dea5251590b0a1c473059d92f4e44491f7f4515ba57e8a0ca160
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .gs/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'cutest', '~> 1.2'
5
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,15 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ clap (1.0.0)
5
+ cutest (1.2.3)
6
+ clap
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ cutest (~> 1.2)
13
+
14
+ BUNDLED WITH
15
+ 1.15.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Steven Weiss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Robert
2
+
3
+ Robert is an implementation of the builder pattern that first runs the given
4
+ arguments through a validation process.
5
+
6
+ ## Installation
7
+
8
+ `gem install robert`
9
+
10
+ ## Usage
11
+
12
+ Robert is helpful in creating form objects (validating Rack params),
13
+ implementing business logic (service objects, if you will) and much more.
14
+ Basically if you do not trust the data, run it through Robert.
15
+
16
+ ```ruby
17
+ class Post
18
+ attr_accessor :title, :body
19
+
20
+ def initialize(attributes)
21
+ attributes.each do |key, value|
22
+ send(:"#{key}=", value)
23
+ end
24
+ end
25
+ end
26
+
27
+ class PostBuilder < Robert
28
+ attr_accessor :title, :body
29
+
30
+ private
31
+
32
+ def validate
33
+ error :title, :is_empty { empty? }
34
+ error :body, :is_empty { empty? }
35
+ end
36
+ end
37
+
38
+ builder = PostBuilder.new(title: 'Title', body: '')
39
+
40
+ result = builder.build(Post)
41
+
42
+ result # false
43
+
44
+ builder.errors # { body: [:is_empty] }
45
+
46
+ builder.body = 'body'
47
+
48
+ result = builder.build(Post)
49
+
50
+ result.is_a?(Post) # true
51
+ ```
52
+
53
+ Check out the tests for more examples.
54
+
55
+ ## API
56
+
57
+ `::build` - Convenience for `::new(params).build(klass)`
58
+
59
+ `::build!` - Convenience for `::new(params).build!(klass)`
60
+
61
+ `#attributes` - Hash of attributes that have been validated (successfully) and "cleaned".
62
+
63
+ `#errors` - Hash of errors.
64
+
65
+ `#build` - Initializes the given class if valid or returns false.
66
+
67
+ `#build!` - Initializes the given class if valid or raises.
68
+
69
+ `#clean` - Template method for cleaning values before assigning them the attributes hash.
70
+
71
+ `#error` - Bread and butter of the library. For a given attribute, adds error if the given block returns false. The block is evaluated in context of the attribute itself.
72
+
73
+ `#validate` - Template method for implementing your validation logic.
74
+
75
+ `#valid?` - Checks to see if the builder is valid.
76
+
77
+ ### Gotchas
78
+
79
+ Sending `#errors` before `#valid?` will return the default errors
80
+ (an empty Hash by default), which will give the appearance of a valid object.
81
+ The reason for this is that if you have an html view and are rendering
82
+ a form/builder's errors, you don't want to `#errors` to trigger validation
83
+ on the initial page load.
84
+
85
+ ## Prior Art
86
+
87
+ Robert is inspired by the following libraries:
88
+
89
+ * [Scrivener](https://github.com/soveran/scrivener)
90
+ * [Hatch](https://github.com/tonchis/hatch)
91
+ * [Django's Forms](https://www.djangoproject.com)
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ desc 'open irb in gs context'
2
+ task :console do
3
+ sh './bin/gs irb -r ./app.rb'
4
+ end
5
+
6
+ task :default do
7
+ sh 'rake -T'
8
+ end
9
+
10
+ desc 'installs gems'
11
+ task :install do
12
+ sh 'mkdir -p .gs & ./bin/gs bundle install --system'
13
+ end
14
+
15
+ desc 'tests the given [test]_test.rb'
16
+ task :test, :name do |t, args|
17
+ name = args[:name] || "*"
18
+
19
+ sh "./bin/gs cutest -r ./lib/robert ./test/#{name}_test.rb"
20
+ end
data/bin/gs ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2012 Michel Martens
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #
23
+ # https://github.com/soveran/gs
24
+
25
+ help = <<EOS
26
+ GS(1)
27
+
28
+ NAME
29
+ gs -- Gemset management
30
+
31
+ SYNOPSIS
32
+ gs init
33
+ gs help
34
+ gs [command]
35
+
36
+ DESCRIPTION
37
+ When called with no arguments, it starts a shell session and
38
+ configures the variables GEM_HOME, GEM_PATH and PATH to point
39
+ to the $PWD/.gs directory. In addition, it sets the GS_NAME
40
+ variable with the name of the current gemset (useful for PS1).
41
+
42
+ init
43
+ Creates the $PWD/.gs directory.
44
+
45
+ help
46
+ Displays this message.
47
+
48
+ [command]
49
+ An optional command that will be executed under gs shell
50
+ session and return to the caller session once finished.
51
+ EOS
52
+
53
+ case ARGV[0]
54
+ when "init" then Dir.mkdir(".gs"); exec($0)
55
+ when "help" then puts help
56
+ else
57
+ if File.directory?(".gs")
58
+ pwd = Dir.pwd
59
+ env = ENV.to_hash
60
+ env["GS_NAME"] = File.basename(pwd)
61
+ env["GEM_HOME"] = pwd + "/.gs"
62
+ env["GEM_PATH"] = pwd + "/.gs:#{`gem env path`.strip}"
63
+ env["PATH"] = pwd + "/.gs/bin:#{ENV['PATH']}"
64
+
65
+ if ARGV.length > 0
66
+ exec env, *ARGV
67
+ else
68
+ exec env, ENV["SHELL"] || ENV["COMSPEC"]
69
+ end
70
+ else
71
+ puts "Directory .gs not found. Try `gs help`."
72
+ exit 1
73
+ end
74
+ end
data/lib/robert.rb ADDED
@@ -0,0 +1,92 @@
1
+ class Robert
2
+ VERSION = '0.1.0'
3
+
4
+ class ValidationError < StandardError; end
5
+
6
+ class << self
7
+ def build(klass, args = {})
8
+ new(args).build(klass)
9
+ end
10
+
11
+ def build!(klass, args = {})
12
+ new(args).build!(klass)
13
+ end
14
+ end
15
+
16
+ attr_reader :attributes, :errors
17
+
18
+ def initialize(args = {})
19
+ args.each do |key, value|
20
+ writer = :"#{key}="
21
+
22
+ if respond_to?(writer)
23
+ send(writer, value)
24
+ end
25
+ end
26
+
27
+ reset
28
+ end
29
+
30
+ def default_attributes
31
+ {}
32
+ end
33
+
34
+ def default_errors
35
+ Hash.new { |h, k| h[k] = [] }
36
+ end
37
+
38
+ def build(klass)
39
+ valid? && klass.new(attributes)
40
+ end
41
+
42
+ def build!(klass)
43
+ build(klass) || raise(ValidationError, errors)
44
+ end
45
+
46
+ def valid?
47
+ reset
48
+
49
+ validate
50
+
51
+ errors.empty?
52
+ end
53
+
54
+ private
55
+
56
+ def add_attribute(key, value)
57
+ attributes[key] = clean(value) unless errors.has_key?(key)
58
+ end
59
+
60
+ def add_error(key, error)
61
+ attributes.delete(key)
62
+
63
+ errors[key] << error
64
+ end
65
+
66
+ def clean(value)
67
+ value
68
+ end
69
+
70
+ def error(key, error, &condition)
71
+ value = send(key)
72
+
73
+ if value.instance_eval &condition
74
+ add_error(key, error)
75
+
76
+ true
77
+ else
78
+ add_attribute(key, value)
79
+
80
+ false
81
+ end
82
+ end
83
+
84
+ def reset
85
+ @attributes = default_attributes
86
+ @errors = default_errors
87
+ end
88
+
89
+ def validate
90
+ raise NotImplementedError
91
+ end
92
+ end
data/robert.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ require_relative './lib/robert'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'robert'
5
+ s.summary = 'Robert'
6
+ s.version = Robert::VERSION
7
+ s.authors = ['Steve Weiss']
8
+ s.email = ['weissst@mail.gvsu.edu']
9
+ s.homepage = 'https://github.com/sirscriptalot/robert'
10
+ s.license = 'MIT'
11
+ s.files = `git ls-files`.split("\n")
12
+
13
+ s.add_development_dependency 'cutest', '~> 1.2'
14
+ end
@@ -0,0 +1,160 @@
1
+ class Email
2
+ LENGTH = 5..255
3
+
4
+ attr_reader :email
5
+
6
+ def initialize(email)
7
+ @email = email
8
+ end
9
+
10
+ def length
11
+ email.length
12
+ end
13
+
14
+ def too_short?
15
+ length < LENGTH.min
16
+ end
17
+
18
+ def too_long?
19
+ length > LENGTH.max
20
+ end
21
+
22
+ def to_s
23
+ email.to_s
24
+ end
25
+ end
26
+
27
+ class User
28
+ attr_accessor :email
29
+
30
+ def initialize(attributes = {})
31
+ attributes.each do |key, value|
32
+ send(:"#{key}=", value)
33
+ end
34
+ end
35
+ end
36
+
37
+ class UserBuilder < Robert
38
+ attr_writer :email
39
+
40
+ def email
41
+ Email.new(@email.to_s)
42
+ end
43
+
44
+ private
45
+
46
+ def clean(value)
47
+ value.to_s
48
+ end
49
+
50
+ def validate
51
+ error :email, :too_short { too_short? } or
52
+ error :email, :too_long { too_long? }
53
+ end
54
+ end
55
+
56
+ test '::build builds object when valid' do
57
+ params = { email: 'valid@example.com' }
58
+
59
+ assert UserBuilder.build(User, params).is_a?(User)
60
+ end
61
+
62
+ test '::build returns false when invalid' do
63
+ params = { email: '' }
64
+
65
+ assert !UserBuilder.build(User, params)
66
+ end
67
+
68
+ test '::build! builds object when valid' do
69
+ params = { email: 'valid@example.com' }
70
+
71
+ assert UserBuilder.build!(User, params).is_a?(User)
72
+ end
73
+
74
+ test '::build! raises when invalid' do
75
+ params = { email: '' }
76
+
77
+ assert_raise Robert::ValidationError do
78
+ UserBuilder.build!(User, params)
79
+ end
80
+ end
81
+
82
+ test '#build builds object when valid' do
83
+ params = { email: 'valid@example.com' }
84
+
85
+ builder = UserBuilder.new(params)
86
+
87
+ assert builder.build(User).is_a?(User)
88
+ end
89
+
90
+ test '#build returns false when invalid' do
91
+ params = { email: '' }
92
+
93
+ builder = UserBuilder.new(params)
94
+
95
+ assert !builder.build(User)
96
+ end
97
+
98
+ test '#build! builds object when valid' do
99
+ params = { email: 'valid@example.com' }
100
+
101
+ builder = UserBuilder.new(params)
102
+
103
+ assert builder.build!(User).is_a?(User)
104
+ end
105
+
106
+ test '#build! raises when invalid' do
107
+ params = { email: '' }
108
+
109
+ builder = UserBuilder.new(params)
110
+
111
+ assert_raise Robert::ValidationError do
112
+ builder.build!(User)
113
+ end
114
+ end
115
+
116
+ test '#valid? captures valid attributes' do
117
+ params = { email: 'valid@example.com' }
118
+
119
+ builder = UserBuilder.new(params)
120
+
121
+ assert !builder.attributes.include?(:email)
122
+
123
+ assert builder.valid?
124
+
125
+ assert builder.attributes.include?(:email)
126
+
127
+ builder.email = ''
128
+
129
+ assert !builder.valid?
130
+
131
+ assert !builder.attributes.include?(:email)
132
+ end
133
+
134
+ test '#valid? captured attributes are cleaned' do
135
+ params = { email: 'valid@example.com' }
136
+
137
+ builder = UserBuilder.new(params)
138
+
139
+ assert builder.valid?
140
+
141
+ assert builder.attributes.include?(:email)
142
+
143
+ assert builder.attributes[:email].is_a?(String)
144
+
145
+ assert !builder.attributes[:email].is_a?(Email)
146
+ end
147
+
148
+ test '#valid? adds errors for conditions' do
149
+ params = { email: '' }
150
+
151
+ builder = UserBuilder.new(params)
152
+
153
+ assert !builder.errors.has_key?(:email)
154
+
155
+ assert !builder.valid?
156
+
157
+ assert builder.errors.has_key?(:email)
158
+
159
+ assert builder.errors[:email].include?(:too_short)
160
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: robert
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Steve Weiss
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cutest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ description:
28
+ email:
29
+ - weissst@mail.gvsu.edu
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - bin/gs
41
+ - lib/robert.rb
42
+ - robert.gemspec
43
+ - test/robert_test.rb
44
+ homepage: https://github.com/sirscriptalot/robert
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.6.11
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Robert
68
+ test_files: []