sullivan 0.0.1 → 0.0.2

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
2
  SHA1:
3
- metadata.gz: b993257c0f51eb41768a9c272798cd6ec6caa160
4
- data.tar.gz: 5e15408676c3ad43fb4645581120072914ae60aa
3
+ metadata.gz: facd94a17e179bef5d7a3caa27a183b1bc855a9c
4
+ data.tar.gz: 1abdb5a1efee46327163f3bf68eb5d43c825ceaa
5
5
  SHA512:
6
- metadata.gz: da18cbc61e3cff2bb8e1715126a9e46dd8ead724d719a5c8534eb3717074fac16568ac9127bc461aeb32515d30981400e925e3c7c4e4511abef429f51ce8e675
7
- data.tar.gz: 13dece6a3657c7e653b87db7847ef42ad6faee4db2c6ee70cc52d62295ab05f08c4781bb3f5913843dee6a4499a6805797a168bd849cb9475773c71ef5ad34d8
6
+ metadata.gz: 79388d55c6f019930dcfb8607927021190678d4967c11daf7ca20a734b18708a93c0f3f161e646430e3791b5b3a92e2bb5cabe96822c588790d64a5600e6648c
7
+ data.tar.gz: c9e6f2c9d92a24844d9e764cc37f30be64000f96947abd5edf3830db7c7fd05d9bf2d5858f609b9616593a3a4f9a46acd82c5c071a1921ce4e214e8c6b22fc57
data/README.md CHANGED
@@ -1,6 +1,78 @@
1
1
  # Sullivan
2
2
 
3
- TODO: Write a gem description
3
+ <img src="doc/img/LouisSullivan.jpg" alt="Louis Sullivan" align="right" />
4
+
5
+ > It is the pervading law of all things organic and inorganic,
6
+ > Of all things physical and metaphysical,
7
+ > Of all things human, and all things super-human,
8
+ > Of all true manifestations of the head,
9
+ > Of the heart, of the soul,
10
+ > That the life is recognizable in its expression,
11
+ > That **form ever follows function**. This is the law.
12
+ > <cite>— Louis Sullivan, 1896</cite>
13
+
14
+ **Sullivan** is a functional, composable, simple way to validate nested data
15
+ structures. It generates validation errors which are especially suitable for
16
+ API responses.
17
+
18
+ Sullivan doesn't do much, because it doesn't need to. It's three things:
19
+
20
+ 1. A simple pattern for defining validators,
21
+ 2. A handful of useful, composable validators provided for free, and
22
+ 3. Some syntactic sugar for using the built-in validators.
23
+
24
+ ## Example
25
+
26
+
27
+ ```ruby
28
+ require 'sullivan'
29
+
30
+ laugh_session_validation = Sullivan.validation do
31
+ laugh = hash(
32
+ sound: string_matching(/\Al(ol)+\z/, error: "must be be a laughing sound of some length"),
33
+ intensity: optional(kind_of(Numeric))
34
+ )
35
+
36
+ hash(
37
+ primary_laugh: laugh,
38
+ rebound_giggles: many(laugh)
39
+ )
40
+ end
41
+
42
+ laugh_session = {
43
+ primary_laugh: {
44
+ sound: "lolololol",
45
+ intensity: "High"
46
+ },
47
+ rebound_giggles: [
48
+ {
49
+ sound: "lololol",
50
+ intensity: 2
51
+ },
52
+ {
53
+ sound: "sigh",
54
+ mood: "pleasant"
55
+ }
56
+ ]
57
+ }
58
+
59
+ laugh_session_validation.validate(laugh_session)
60
+
61
+ # =>
62
+ # {
63
+ # :primary_laugh => {
64
+ # :intensity => "must be a kind of Numeric, if present"
65
+ # },
66
+ # :rebound_giggles => [
67
+ # nil,
68
+ # {
69
+ # :sound => "must be be a laughing sound of some length",
70
+ # :mood => "is unexpected"
71
+ # }
72
+ # ]
73
+ # }
74
+ ```
75
+
4
76
 
5
77
  ## Installation
6
78
 
@@ -18,11 +90,161 @@ Or install it yourself as:
18
90
 
19
91
  ## Usage
20
92
 
21
- TODO: Write usage instructions here
93
+ Like it says above, Sullivan is three things.
94
+
95
+ ### `validate`: A simple pattern for defining validators.
96
+
97
+ Sullivan specifies a simple API for defining validators. It's so simple, you
98
+ don't need Sullivan to use it. All you need is an object which responds to
99
+ `#validate`. If it fails to validate, it should return an error message (a
100
+ string). If it passes validation, it should return `nil`. That's all there is
101
+ to it.
102
+
103
+ If your validator takes parameters, it might make sense to write it as a class
104
+ and instantiate it:
105
+
106
+
107
+ ```ruby
108
+ class LegalVotingAge
109
+ def initialize(country:)
110
+ @minimum_age =
111
+ case country
112
+ when :united_states
113
+ 18
114
+ when :austria
115
+ 16
116
+ else
117
+ raise "Don't know the voting age in #{country}"
118
+ end
119
+ end
120
+
121
+ def validate(age)
122
+ "is too young to vote" if age < @minimum_age
123
+ end
124
+ end
125
+
126
+ LegalVotingAge.new(:united_states).validate(17) #=> "is too young to vote"
127
+ LegalVotingAge.new(:austria).validate(17) #=> nil
128
+ ```
129
+
130
+ If your validator doesn't take parameters, you might want to just make it an
131
+ object:
132
+
133
+ ```ruby
134
+ ApiBoolean = Object.tap do |v|
135
+ def v.validate(value)
136
+ "must be a boolean value" unless [true, false, 'true', 'false'].include?(value)
137
+ end
138
+ end
139
+
140
+ ApiBoolean.validate(true) #=> nil
141
+ ApiBoolean.validate('false') #=> nil
142
+ ApiBoolean.validate('not sure') #=> "must be a boolean value"
143
+ ```
144
+
145
+ Sullivan doesn't care. In fact, Sullivan-the-libary isn't even involved yet.
146
+
147
+ ### `Sullivan::Validators`: A handful of useful, composable validators provided for free
148
+
149
+ Sometimes you need a really custom validator, but there are a few staples we
150
+ need in all sorts of projects. Sullivan provides those, including some
151
+ **higher-order validators** (validators which take other validators as
152
+ parameters, like `Hash` and `Optional`), which is where Sullivan's
153
+ composability really shines.
154
+
155
+ Sullivan's built-in validators live in `Sullivan::Validators`. Look there to
156
+ read more about each one.
157
+
158
+
159
+ ### `Sullivan.validation`: Some syntactic sugar for using the built-in validators
160
+
161
+ The built-in validators are a bit cumbersome to instantiate, considering you'll
162
+ be using them quite a bit. To help, there's `Sullivan.validation`. Within its
163
+ block, you can instantiate the validators in `Sullivan::Validators` as
164
+ `snake_cased` methods. So:
165
+
166
+ ```ruby
167
+ v = Sullivan.validation do
168
+ hash({})
169
+ end
170
+
171
+ v.class #=> Sullivan::Validators::Hash
172
+ ```
173
+
174
+ There's one catch: because this uses `instance_eval`, inside the block `self`
175
+ will not be the same as `self` outside the block, so you can't use method calls
176
+ the way you might like to. That is, this won't work:
177
+
178
+ ```ruby
179
+ class User
180
+ def valid_username_regex
181
+ %r{\w+}
182
+ end
183
+
184
+ def validation
185
+ Sullivan.validation do
186
+ string_matching(valid_username_regex)
187
+ #=> NameError: undefined local variable or method `valid_username_regex' for #<Sullivan::DSL:0x007fa5c44e1508>
188
+ end
189
+ end
190
+ end
191
+ ```
192
+
193
+ If you need to do something like that, you can use the 1-arity form of the block, for a slightly more verbose syntax:
194
+
195
+ ```ruby
196
+ class User
197
+ def valid_username_regex
198
+ %r{\w+}
199
+ end
200
+
201
+ def validation
202
+ Sullivan.validation do |vals|
203
+ vals.string_matching(valid_username_regex)
204
+ end
205
+ end
206
+ end
207
+ ```
208
+
209
+ Of course, `Sullivan.validation` is completely optional. Feel free to instantiate the validation classes directly.
210
+
211
+
212
+ ### Composition: Bringing it all together
213
+
214
+ This is where it gets fun. Let's say you're validating API input. Suppose you
215
+ have an API that can create a Person record and one that can create multiple
216
+ Person records at once. You might have validations like:
217
+
218
+ ```ruby
219
+ module Validations
220
+ Person = Sullivan.validation do
221
+ hash(
222
+ name: kind_of(String),
223
+ favorite_ice_cream_flavor: optional(kind_of(String))
224
+ )
225
+ end
226
+
227
+ PersonCreation = Sullivan.validation do
228
+ hash(person: Person)
229
+ end
230
+
231
+ PeopleCreation = Sullivan.validation do
232
+ hash(people: many(Person))
233
+ end
234
+ end
235
+ ```
236
+
237
+ Now your API could use `Validations::PersonCreation.validate` and `Validations::PeopleCreation.validate`
238
+ to validate the two kinds of requests.
239
+
240
+ Notice that I've assigned these validators to constants in a module. That's a
241
+ useful pattern in some cases, but it's completely optional. Store them wherever
242
+ they're most useful in your application. They're just objects.
243
+
22
244
 
23
245
  ## Contributing
24
246
 
25
- 1. Fork it ( http://github.com/<my-github-username>/sullivan/fork )
247
+ 1. Fork it ( http://github.com/Peeja/sullivan/fork )
26
248
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
249
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
250
  4. Push to the branch (`git push origin my-new-feature`)
@@ -1,6 +1,15 @@
1
1
  module Sullivan
2
2
  def self.validation(&block)
3
- DSL.new.instance_eval(&block)
3
+ dsl = DSL.new
4
+
5
+ case block.arity
6
+ when 0
7
+ dsl.instance_eval(&block)
8
+ when 1
9
+ block.call(dsl)
10
+ else
11
+ raise ArgumentError.new("Sullivan.validation's block must have an arity of 0 or 1.")
12
+ end
4
13
  end
5
14
 
6
15
  class DSL < BasicObject
@@ -1,3 +1,3 @@
1
1
  module Sullivan
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -6,7 +6,7 @@ describe Sullivan do
6
6
  v = Sullivan.validation do
7
7
  hash(
8
8
  string_matching: string_matching(/\Al(ol)+\z/, error: "must be be a laugh"),
9
- kind_of: kind_of(Numeric),
9
+ kind_of: kind_of(Numeric)
10
10
  )
11
11
  end
12
12
 
@@ -15,5 +15,25 @@ describe Sullivan do
15
15
  expect(error[:string_matching]).to eq("must be be a laugh")
16
16
  expect(error[:kind_of]).to eq("must be a kind of Numeric")
17
17
  end
18
+
19
+ it "uses a non-instance-eval version when the block has an arity of 1" do
20
+ v = Sullivan.validation do |vals|
21
+ expect(a_method_on_self).to eq("can be called without an error")
22
+
23
+ vals.hash(
24
+ string_matching: vals.string_matching(/\Al(ol)+\z/, error: "must be be a laugh"),
25
+ kind_of: vals.kind_of(Numeric)
26
+ )
27
+ end
28
+
29
+ error = v.validate({})
30
+
31
+ expect(error[:string_matching]).to eq("must be be a laugh")
32
+ expect(error[:kind_of]).to eq("must be a kind of Numeric")
33
+ end
34
+
35
+ def a_method_on_self
36
+ "can be called without an error"
37
+ end
18
38
  end
19
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sullivan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Jaros
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-07 00:00:00.000000000 Z
11
+ date: 2014-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler