naught 0.0.2 → 0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -0
  4. data/Changelog.md +6 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +10 -0
  7. data/README.markdown +413 -0
  8. data/Rakefile +5 -0
  9. data/lib/naught.rb +1 -4
  10. data/lib/naught/null_class_builder.rb +37 -137
  11. data/lib/naught/null_class_builder/command.rb +20 -0
  12. data/lib/naught/null_class_builder/commands.rb +8 -0
  13. data/lib/naught/null_class_builder/commands/define_explicit_conversions.rb +13 -24
  14. data/lib/naught/null_class_builder/commands/define_implicit_conversions.rb +14 -0
  15. data/lib/naught/null_class_builder/commands/impersonate.rb +9 -0
  16. data/lib/naught/null_class_builder/commands/mimic.rb +40 -0
  17. data/lib/naught/null_class_builder/commands/pebble.rb +36 -0
  18. data/lib/naught/null_class_builder/commands/predicates_return.rb +47 -0
  19. data/lib/naught/null_class_builder/commands/singleton.rb +24 -0
  20. data/lib/naught/null_class_builder/commands/traceable.rb +19 -0
  21. data/lib/naught/null_class_builder/conversions_module.rb +57 -0
  22. data/lib/naught/version.rb +1 -1
  23. data/naught.gemspec +2 -1
  24. data/spec/base_object_spec.rb +47 -0
  25. data/spec/basic_null_object_spec.rb +35 -0
  26. data/spec/blackhole_spec.rb +16 -0
  27. data/spec/conversions_spec.rb +20 -0
  28. data/spec/functions/actual_spec.rb +22 -0
  29. data/spec/functions/just_spec.rb +22 -0
  30. data/spec/functions/maybe_spec.rb +35 -0
  31. data/spec/functions/null_spec.rb +34 -0
  32. data/spec/implicit_conversions_spec.rb +25 -0
  33. data/spec/mimic_spec.rb +122 -0
  34. data/spec/naught/null_object_builder/command_spec.rb +10 -0
  35. data/spec/naught/null_object_builder_spec.rb +31 -0
  36. data/spec/naught_spec.rb +77 -411
  37. data/spec/pebble_spec.rb +75 -0
  38. data/spec/predicate_spec.rb +80 -0
  39. data/spec/singleton_null_object_spec.rb +35 -0
  40. data/spec/spec_helper.rb +13 -1
  41. data/spec/support/convertable_null.rb +4 -0
  42. metadata +76 -32
  43. data/.rspec +0 -1
  44. data/README.org +0 -340
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ module Naught
4
+ describe NullClassBuilder::Command do
5
+ it 'is abstract' do
6
+ command = NullClassBuilder::Command.new(nil)
7
+ expect{command.call}.to raise_error(NotImplementedError)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module Naught
4
+ class NullClassBuilder
5
+ module Commands
6
+ class TestCommand
7
+ end
8
+ end
9
+ end
10
+
11
+ describe NullClassBuilder do
12
+ subject(:builder) { NullClassBuilder.new }
13
+ it 'responds to commands defined in NullObjectBuilder::Commands' do
14
+ expect(builder).to respond_to(:test_command)
15
+ end
16
+
17
+ it 'translates method calls into command invocations including arguments' do
18
+ test_command = double
19
+ NullClassBuilder::Commands::TestCommand.should_receive(:new).
20
+ with(builder, "foo", 42).
21
+ and_return(test_command)
22
+ test_command.should_receive(:call).and_return("COMMAND RESULT")
23
+ expect(builder.test_command("foo", 42)).to eq("COMMAND RESULT")
24
+ end
25
+
26
+ it 'handles missing non-command missing methods normally' do
27
+ expect(builder).not_to respond_to(:nonexistant_method)
28
+ expect{builder.nonexistent_method}.to raise_error(NoMethodError)
29
+ end
30
+ end
31
+ end
@@ -1,429 +1,95 @@
1
1
  require 'spec_helper'
2
- require 'naught'
3
2
 
4
- module Naught
5
- describe 'basic null object' do
6
- subject(:null) { null_class.new }
7
- let(:null_class) {
8
- Naught.build
9
- }
10
- it 'responds to arbitrary messages and returns nil' do
11
- expect(null.info).to be_nil
12
- expect(null.foobaz).to be_nil
13
- expect(null.to_s).to be_nil
14
- end
15
-
16
- it 'accepts any arguments for any messages' do
17
- null.foobaz(1,2,3)
18
- end
19
- it 'reports that it responds to any message' do
20
- expect(null).to respond_to(:info)
21
- expect(null).to respond_to(:foobaz)
22
- expect(null).to respond_to(:to_s)
23
- end
24
- it 'can be inspected' do
25
- expect(null.inspect).to eq("<null>")
26
- end
27
- it 'knows its own class' do
28
- expect(null.class).to eq(null_class)
29
- end
30
- it 'aliases .new to .get' do
31
- expect(null_class.get.class).to be(null_class)
32
- end
3
+ describe 'null object impersonating another type' do
4
+ class Point
5
+ def x; 23; end
6
+ def y; 42; end
33
7
  end
34
- describe 'explicitly convertable null object' do
35
- subject(:null) { null_class.new }
36
- let(:null_class) {
37
- Naught.build do |b|
38
- b.define_explicit_conversions
39
- end
40
- }
41
-
42
- it "defines common explicit conversions to return zero values" do
43
- expect(null.to_s).to eq("")
44
- expect(null.to_a).to eq([])
45
- expect(null.to_i).to eq(0)
46
- expect(null.to_f).to eq(0.0)
47
- expect(null.to_c).to eq(Complex(0))
48
- expect(null.to_r).to eq(Rational(0))
49
- expect(null.to_h).to eq({})
8
+
9
+ subject(:null) { impersonation_class.new }
10
+ let(:impersonation_class) {
11
+ Naught.build do |b|
12
+ b.impersonate Point
50
13
  end
14
+ }
15
+
16
+ it 'matches the impersonated type' do
17
+ expect(Point).to be === null
51
18
  end
52
- describe 'implicitly convertable null object' do
53
- subject(:null) { null_class.new }
54
- let(:null_class) {
55
- Naught.build do |b|
56
- b.define_implicit_conversions
57
- end
58
- }
59
- it 'implicitly splats the same way an empty array does' do
60
- a, b = null
61
- expect(a).to be_nil
62
- expect(b).to be_nil
63
- end
64
- it 'is implicitly convertable to String' do
65
- expect(eval(null)).to be_nil
66
- end
67
- it 'implicitly converts to an empty array' do
68
- expect(null.to_ary).to eq([])
69
- end
70
- it 'implicitly converts to an empty string' do
71
- expect(null.to_str).to eq("")
72
- end
73
-
19
+
20
+ it 'responds to methods from the impersonated type' do
21
+ expect(null.x).to be_nil
22
+ expect(null.y).to be_nil
74
23
  end
75
- describe 'singleton null object' do
76
- subject(:null_class) {
77
- Naught.build do |b|
78
- b.singleton
79
- end
80
- }
81
-
82
- it 'does not respond to .new' do
83
- expect{ null_class.new }.to raise_error
84
- end
85
-
86
- it 'has only one instance' do
87
- null1 = null_class.instance
88
- null2 = null_class.instance
89
- expect(null1).to be(null2)
90
- end
91
-
92
- it 'can be cloned' do
93
- null = null_class.instance
94
- expect(null.clone).to be_nil
95
- end
96
-
97
- it 'can be duplicated' do
98
- null = null_class.instance
99
- expect(null.dup).to be_nil
100
- end
101
- it 'aliases .instance to .get' do
102
- expect(null_class.get).to be null_class.instance
103
- end
104
- it 'permits arbitrary arguments to be passed to .get' do
105
- null_class.get(42, foo: "bar")
106
- end
24
+
25
+ it 'does not respond to unknown methods' do
26
+ expect{null.foo}.to raise_error(NoMethodError)
107
27
  end
108
- describe 'black hole null object' do
109
- subject(:null) { null_class.new }
110
- let(:null_class) {
111
- Naught.build do |b|
112
- b.black_hole
113
- end
114
- }
115
-
116
- it 'returns self from arbitray method calls' do
117
- expect(null.info).to be(null)
118
- expect(null.foobaz).to be(null)
119
- expect(null << "bar").to be(null)
120
- end
28
+ end
29
+
30
+ describe 'traceable null object' do
31
+ subject(:trace_null) {
32
+ null_object_and_line.first
33
+ }
34
+ let(:null_object_and_line) {
35
+ obj = trace_null_class.new; line = __LINE__;
36
+ [obj, line]
37
+ }
38
+ let(:instantiation_line) { null_object_and_line.last }
39
+ let(:trace_null_class) {
40
+ Naught.build do |b|
41
+ b.traceable
42
+ end
43
+ }
44
+
45
+ it 'remembers the file it was instantiated from' do
46
+ expect(trace_null.__file__).to eq(__FILE__)
121
47
  end
122
- describe 'null object mimicking a class' do
123
- class User
124
- def login
125
- "bob"
126
- end
127
- end
128
-
129
- module Authorizable
130
- def authorized_for?(object)
131
- true
132
- end
133
- end
134
-
135
- class LibraryPatron < User
136
- include Authorizable
137
-
138
- def member?; true; end
139
- def name; "Bob"; end
140
- def notify_of_overdue_books(titles)
141
- puts "Notifying Bob his books are overdue..."
142
- end
143
- end
144
-
145
- subject(:null) { mimic_class.new }
146
- let(:mimic_class) {
147
- Naught.build do |b|
148
- b.mimic LibraryPatron
149
- end
150
- }
151
- it 'responds to all methods defined on the target class' do
152
- expect(null.member?).to be_nil
153
- expect(null.name).to be_nil
154
- expect(null.notify_of_overdue_books(['The Grapes of Wrath'])).to be_nil
155
- end
156
-
157
- it 'does not respond to methods not defined on the target class' do
158
- expect{null.foobar}.to raise_error(NoMethodError)
159
- end
160
-
161
- it 'reports which messages it does and does not respond to' do
162
- expect(null).to respond_to(:member?)
163
- expect(null).to respond_to(:name)
164
- expect(null).to respond_to(:notify_of_overdue_books)
165
- expect(null).not_to respond_to(:foobar)
166
- end
167
- it 'has an informative inspect string' do
168
- expect(null.inspect).to eq("<null:Naught::LibraryPatron>")
169
- end
170
-
171
- it 'excludes Object methods from being mimicked' do
172
- expect(null.object_id).not_to be_nil
173
- expect(null.hash).not_to be_nil
174
- end
175
-
176
- it 'includes inherited methods' do
177
- expect(null.authorized_for?('something')).to be_nil
178
- expect(null.login).to be_nil
179
- end
180
-
181
- describe 'with include_super: false' do
182
- let(:mimic_class) {
183
- Naught.build do |b|
184
- b.mimic LibraryPatron, include_super: false
185
- end
186
- }
187
-
188
- it 'excludes inherited methods' do
189
- expect(null).to_not respond_to(:authorized_for?)
190
- expect(null).to_not respond_to(:login)
191
- end
192
- end
48
+
49
+ it 'remembers the line it was instantiated from' do
50
+ expect(trace_null.__line__).to eq(instantiation_line)
193
51
  end
194
- describe 'using mimic with black_hole' do
195
- require 'logger'
196
- subject(:null) { mimic_class.new }
197
- let(:mimic_class) {
198
- Naught.build do |b|
199
- b.mimic Logger
200
- b.black_hole
201
- end
202
- }
203
-
204
- def self.it_behaves_like_a_black_hole_mimic
205
- it 'returns self from mimicked methods' do
206
- expect(null.info).to equal(null)
207
- expect(null.error).to equal(null)
208
- expect(null << "test").to equal(null)
209
- end
210
-
211
- it 'does not respond to methods not defined on the target class' do
212
- expect{null.foobar}.to raise_error(NoMethodError)
213
- end
214
- end
215
-
216
- it_behaves_like_a_black_hole_mimic
217
-
218
- describe '(reverse order)' do
219
- let(:mimic_class) {
220
- Naught.build do |b|
221
- b.black_hole
222
- b.mimic Logger
223
- end
224
- }
225
-
226
- it_behaves_like_a_black_hole_mimic
227
- end
52
+
53
+ def make_null
54
+ trace_null_class.get(caller: caller(1))
228
55
  end
229
- describe 'null object impersonating another type' do
230
- class Point
231
- def x; 23; end
232
- def y; 42; end
233
- end
234
-
235
- subject(:null) { impersonation_class.new }
236
- let(:impersonation_class) {
237
- Naught.build do |b|
238
- b.impersonate Point
239
- end
240
- }
241
-
242
- it 'matches the impersonated type' do
243
- expect(Point).to be === null
244
- end
245
-
246
- it 'responds to methods from the impersonated type' do
247
- expect(null.x).to be_nil
248
- expect(null.y).to be_nil
249
- end
250
-
251
- it 'does not respond to unknown methods' do
252
- expect{null.foo}.to raise_error(NoMethodError)
253
- end
56
+
57
+ it 'can accept custom backtrace info' do
58
+ obj = make_null; line = __LINE__
59
+ expect(obj.__line__).to eq(line)
254
60
  end
255
- describe 'traceable null object' do
256
- subject(:trace_null) {
257
- null_object_and_line.first
258
- }
259
- let(:null_object_and_line) {
260
- obj = trace_null_class.new; line = __LINE__;
261
- [obj, line]
262
- }
263
- let(:instantiation_line) { null_object_and_line.last }
264
- let(:trace_null_class) {
265
- Naught.build do |b|
266
- b.traceable
61
+ end
62
+
63
+ describe 'customized null object' do
64
+ subject(:custom_null) { custom_null_class.new }
65
+ let(:custom_null_class) {
66
+ Naught.build do |b|
67
+ b.define_explicit_conversions
68
+ def to_path
69
+ "/dev/null"
267
70
  end
268
- }
269
-
270
- it 'remembers the file it was instantiated from' do
271
- expect(trace_null.__file__).to eq(__FILE__)
272
- end
273
-
274
- it 'remembers the line it was instantiated from' do
275
- expect(trace_null.__line__).to eq(instantiation_line)
276
- end
277
- def make_null
278
- trace_null_class.get(caller: caller(1))
279
- end
280
-
281
- it 'can accept custom backtrace info' do
282
- obj = make_null; line = __LINE__
283
- expect(obj.__line__).to eq(line)
284
- end
285
- end
286
- describe 'customized null object' do
287
- subject(:custom_null) { custom_null_class.new }
288
- let(:custom_null_class) {
289
- Naught.build do |b|
290
- b.define_explicit_conversions
291
- def to_path
292
- "/dev/null"
293
- end
294
- def to_s
295
- "NOTHING TO SEE HERE"
296
- end
71
+ def to_s
72
+ "NOTHING TO SEE HERE"
297
73
  end
298
- }
299
-
300
- it 'responds to custom-defined methods' do
301
- expect(custom_null.to_path).to eq("/dev/null")
302
- end
303
-
304
- it 'allows generated methods to be overridden' do
305
- expect(custom_null.to_s).to eq("NOTHING TO SEE HERE")
306
- end
307
- end
308
- TestNull = Naught.build
309
-
310
- describe 'a named null object class' do
311
- it 'has named ancestor modules' do
312
- expect(TestNull.ancestors[0..2].map(&:name)).to eq([
313
- 'Naught::TestNull',
314
- 'Naught::TestNull::Customizations',
315
- 'Naught::TestNull::GeneratedMethods'
316
- ])
317
- end
318
- end
319
- ConvertableNull = Naught.build do |b|
320
- b.null_equivalents << ""
321
- b.traceable
322
- end
323
-
324
- describe 'Null()' do
325
- include ConvertableNull::Conversions
326
-
327
- specify 'given no input, returns a null object' do
328
- expect(Null().class).to be(ConvertableNull)
329
- end
330
-
331
- specify 'given nil, returns a null object' do
332
- expect(Null(nil).class).to be(ConvertableNull)
333
- end
334
-
335
- specify 'given a null object, returns the same null object' do
336
- null = ConvertableNull.get
337
- expect(Null(null)).to be(null)
338
- end
339
-
340
- specify 'given anything in null_equivalents, return a null object' do
341
- expect(Null("").class).to be(ConvertableNull)
342
- end
343
-
344
- specify 'given anything else, raises an ArgumentError' do
345
- expect{Null(false)}.to raise_error(ArgumentError)
346
- expect{Null("hello")}.to raise_error(ArgumentError)
347
- end
348
-
349
- it 'generates null objects with useful trace info' do
350
- null = Null(); line = __LINE__
351
- expect(null.__line__).to eq(line)
352
- expect(null.__file__).to eq(__FILE__)
353
- end
354
-
355
- end
356
- describe 'Maybe()' do
357
- include ConvertableNull::Conversions
358
-
359
- specify 'given nil, returns a null object' do
360
- expect(Maybe(nil).class).to be(ConvertableNull)
361
- end
362
-
363
- specify 'given a null object, returns the same null object' do
364
- null = ConvertableNull.get
365
- expect(Maybe(null)).to be(null)
366
- end
367
-
368
- specify 'given anything in null_equivalents, return a null object' do
369
- expect(Maybe("").class).to be(ConvertableNull)
370
- end
371
-
372
- specify 'given anything else, returns the input unchanged' do
373
- expect(Maybe(false)).to be(false)
374
- str = "hello"
375
- expect(Maybe(str)).to be(str)
376
- end
377
-
378
- it 'generates null objects with useful trace info' do
379
- null = Maybe(); line = __LINE__
380
- expect(null.__line__).to eq(line)
381
- expect(null.__file__).to eq(__FILE__)
382
- end
383
-
384
- it 'also works with blocks' do
385
- expect(Maybe{nil}.class).to eq(ConvertableNull)
386
- expect(Maybe{"foo"}).to eq("foo")
387
74
  end
75
+ }
76
+
77
+ it 'responds to custom-defined methods' do
78
+ expect(custom_null.to_path).to eq("/dev/null")
388
79
  end
389
- describe 'Just()' do
390
- include ConvertableNull::Conversions
391
-
392
- specify 'passes non-nullish values through' do
393
- expect(Just(false)).to be(false)
394
- str = "hello"
395
- expect(Just(str)).to be(str)
396
- end
397
-
398
- specify 'rejects nullish values' do
399
- expect{Just(nil)}.to raise_error(ArgumentError)
400
- expect{Just("")}.to raise_error(ArgumentError)
401
- expect{Just(ConvertableNull.get)}.to raise_error(ArgumentError)
402
- end
403
-
404
- it 'also works with blocks' do
405
- expect{Just{nil}.class}.to raise_error(ArgumentError)
406
- expect(Just{"foo"}).to eq("foo")
407
- end
80
+
81
+ it 'allows generated methods to be overridden' do
82
+ expect(custom_null.to_s).to eq("NOTHING TO SEE HERE")
408
83
  end
409
- describe 'Actual()' do
410
- include ConvertableNull::Conversions
411
-
412
- specify 'given a null object, returns nil' do
413
- null = ConvertableNull.get
414
- expect(Actual(null)).to be_nil
415
- end
416
-
417
- specify 'given anything else, returns the input unchanged' do
418
- expect(Actual(false)).to be(false)
419
- str = "hello"
420
- expect(Actual(str)).to be(str)
421
- expect(Actual(nil)).to be_nil
422
- end
423
-
424
- it 'also works with blocks' do
425
- expect(Actual{ConvertableNull.new}).to be_nil
426
- expect(Actual{"foo"}).to eq("foo")
427
- end
84
+ end
85
+ TestNull = Naught.build
86
+
87
+ describe 'a named null object class' do
88
+ it 'has named ancestor modules' do
89
+ expect(TestNull.ancestors[0..2].map(&:name)).to eq([
90
+ 'TestNull',
91
+ 'TestNull::Customizations',
92
+ 'TestNull::GeneratedMethods'
93
+ ])
428
94
  end
429
95
  end