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,47 @@
1
+ require 'naught/null_class_builder/command'
2
+
3
+ module Naught::NullClassBuilder::Commands
4
+ class PredicatesReturn < Naught::NullClassBuilder::Command
5
+ def initialize(builder, return_value)
6
+ super(builder)
7
+ @predicate_return_value = return_value
8
+ end
9
+
10
+ def call
11
+ defer do |subject|
12
+ define_method_missing(subject)
13
+ define_predicate_methods(subject)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def define_method_missing(subject)
20
+ subject.module_exec(@predicate_return_value) do |return_value|
21
+ if subject.method_defined?(:method_missing)
22
+ original_method_missing = instance_method(:method_missing)
23
+ define_method(:method_missing) do
24
+ |method_name, *args, &block|
25
+ if method_name.to_s.end_with?('?')
26
+ return_value
27
+ else
28
+ original_method_missing.bind(self).call(method_name, *args, &block)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def define_predicate_methods(subject)
36
+ subject.module_exec(@predicate_return_value) do |return_value|
37
+ instance_methods.each do |method_name|
38
+ if method_name.to_s.end_with?('?')
39
+ define_method(method_name) do |*|
40
+ return_value
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ require 'naught/null_class_builder/command'
2
+
3
+ module Naught::NullClassBuilder::Commands
4
+ class Singleton < Naught::NullClassBuilder::Command
5
+ def call
6
+ defer(class: true) do |subject|
7
+ require 'singleton'
8
+ subject.module_eval do
9
+ include ::Singleton
10
+
11
+ def self.get(*)
12
+ instance
13
+ end
14
+
15
+ %w(dup clone).each do |method_name|
16
+ define_method method_name do
17
+ self
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require 'naught/null_class_builder/command'
2
+
3
+ module Naught::NullClassBuilder::Commands
4
+ class Traceable < Naught::NullClassBuilder::Command
5
+ def call
6
+ defer do |subject|
7
+ subject.module_eval do
8
+ attr_reader :__file__, :__line__
9
+
10
+ def initialize(options={})
11
+ backtrace = options.fetch(:caller) { Kernel.caller(4) }
12
+ @__file__, line, _ = backtrace[0].split(':')
13
+ @__line__ = line.to_i
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ module Naught
2
+ class ConversionsModule < Module
3
+ attr_reader :null_class
4
+ attr_reader :null_equivs
5
+
6
+ def initialize(null_class, null_equivs)
7
+ @null_class = null_class
8
+ @null_equivs = null_equivs
9
+ mod = self
10
+ super() do
11
+ %w[Null Maybe Just Actual].each do |method_name|
12
+ define_method(method_name, &mod.method(method_name))
13
+ end
14
+ end
15
+ end
16
+
17
+ def Null(object=:nothing_passed)
18
+ case object
19
+ when NullObjectTag then object
20
+ when :nothing_passed, *null_equivs
21
+ null_class.get(caller: caller(1))
22
+ else raise ArgumentError, "#{object.inspect} is not null!"
23
+ end
24
+ end
25
+
26
+ def Maybe(object=nil, &block)
27
+ object = block ? block.call : object
28
+ case object
29
+ when NullObjectTag then object
30
+ when *null_equivs
31
+ null_class.get(caller: caller(1))
32
+ else
33
+ object
34
+ end
35
+ end
36
+
37
+ def Just(object=nil, &block)
38
+ object = block ? block.call : object
39
+ case object
40
+ when NullObjectTag, *null_equivs
41
+ raise ArgumentError, "Null value: #{object.inspect}"
42
+ else
43
+ object
44
+ end
45
+ end
46
+
47
+ def Actual(object=nil, &block)
48
+ object = block ? block.call : object
49
+ case object
50
+ when NullObjectTag then nil
51
+ else
52
+ object
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -1,3 +1,3 @@
1
1
  module Naught
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -20,7 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
24
  spec.add_development_dependency "guard"
25
25
  spec.add_development_dependency "guard-rspec"
26
+ spec.add_development_dependency "guard-bundler"
26
27
  end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'null object with a custom base class' do
4
+
5
+ subject(:null) { custom_base_null_class.new }
6
+
7
+ let(:custom_base_null_class) do
8
+ Naught.build do |b|
9
+ b.base_class = Object
10
+ end
11
+ end
12
+
13
+ it 'respond to base class methods' do
14
+ expect(null.methods).to be_a_kind_of(Array)
15
+ end
16
+
17
+ it 'respond to unknown methods' do
18
+ expect(null.foo).to be_nil
19
+ end
20
+
21
+ it 'exposes the default base class choice, for the curious' do
22
+ default_base_class = :not_set
23
+ Naught.build do |b|
24
+ default_base_class = b.base_class
25
+ end
26
+ expect(default_base_class).to eq(BasicObject)
27
+ end
28
+
29
+ describe 'singleton null object' do
30
+ subject(:null_instance) { custom_base_singleton_null_class.instance }
31
+
32
+ let(:custom_base_singleton_null_class) do
33
+ Naught.build do |b|
34
+ b.singleton
35
+ b.base_class = Object
36
+ end
37
+ end
38
+
39
+ it 'can be cloned' do
40
+ expect(null_instance.clone).to be(null_instance)
41
+ end
42
+
43
+ it 'can be duplicated' do
44
+ expect(null_instance.dup).to be(null_instance)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'basic null object' do
4
+ let(:null_class) { Naught.build }
5
+ subject(:null) { null_class.new }
6
+
7
+ it 'responds to arbitrary messages and returns nil' do
8
+ expect(null.info).to be_nil
9
+ expect(null.foobaz).to be_nil
10
+ expect(null.to_s).to be_nil
11
+ end
12
+
13
+ it 'accepts any arguments for any messages' do
14
+ null.foobaz(1,2,3)
15
+ end
16
+
17
+ it 'reports that it responds to any message' do
18
+ expect(null).to respond_to(:info)
19
+ expect(null).to respond_to(:foobaz)
20
+ expect(null).to respond_to(:to_s)
21
+ end
22
+
23
+ it 'can be inspected' do
24
+ expect(null.inspect).to eq("<null>")
25
+ end
26
+
27
+ it 'knows its own class' do
28
+ expect(null.class).to eq(null_class)
29
+ end
30
+
31
+ it 'aliases .new to .get' do
32
+ expect(null_class.get.class).to be(null_class)
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'black hole null object' do
4
+ subject(:null) { null_class.new }
5
+ let(:null_class) {
6
+ Naught.build do |b|
7
+ b.black_hole
8
+ end
9
+ }
10
+
11
+ it 'returns self from arbitray method calls' do
12
+ expect(null.info).to be(null)
13
+ expect(null.foobaz).to be(null)
14
+ expect(null << "bar").to be(null)
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe 'explicitly convertable null object' do
4
+ let(:null_class) {
5
+ Naught.build do |b|
6
+ b.define_explicit_conversions
7
+ end
8
+ }
9
+ subject(:null) { null_class.new }
10
+
11
+ it "defines common explicit conversions to return zero values" do
12
+ expect(null.to_s).to eq("")
13
+ expect(null.to_a).to eq([])
14
+ expect(null.to_i).to eq(0)
15
+ expect(null.to_f).to eq(0.0)
16
+ expect(null.to_c).to eq(Complex(0))
17
+ expect(null.to_r).to eq(Rational(0))
18
+ expect(null.to_h).to eq({})
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Actual()' do
4
+ include ConvertableNull::Conversions
5
+
6
+ specify 'given a null object, returns nil' do
7
+ null = ConvertableNull.get
8
+ expect(Actual(null)).to be_nil
9
+ end
10
+
11
+ specify 'given anything else, returns the input unchanged' do
12
+ expect(Actual(false)).to be(false)
13
+ str = "hello"
14
+ expect(Actual(str)).to be(str)
15
+ expect(Actual(nil)).to be_nil
16
+ end
17
+
18
+ it 'also works with blocks' do
19
+ expect(Actual{ConvertableNull.new}).to be_nil
20
+ expect(Actual{"foo"}).to eq("foo")
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Just()' do
4
+ include ConvertableNull::Conversions
5
+
6
+ specify 'passes non-nullish values through' do
7
+ expect(Just(false)).to be(false)
8
+ str = "hello"
9
+ expect(Just(str)).to be(str)
10
+ end
11
+
12
+ specify 'rejects nullish values' do
13
+ expect{Just(nil)}.to raise_error(ArgumentError)
14
+ expect{Just("")}.to raise_error(ArgumentError)
15
+ expect{Just(ConvertableNull.get)}.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'also works with blocks' do
19
+ expect{Just{nil}.class}.to raise_error(ArgumentError)
20
+ expect(Just{"foo"}).to eq("foo")
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Maybe()' do
4
+ include ConvertableNull::Conversions
5
+
6
+ specify 'given nil, returns a null object' do
7
+ expect(Maybe(nil).class).to be(ConvertableNull)
8
+ end
9
+
10
+ specify 'given a null object, returns the same null object' do
11
+ null = ConvertableNull.get
12
+ expect(Maybe(null)).to be(null)
13
+ end
14
+
15
+ specify 'given anything in null_equivalents, return a null object' do
16
+ expect(Maybe("").class).to be(ConvertableNull)
17
+ end
18
+
19
+ specify 'given anything else, returns the input unchanged' do
20
+ expect(Maybe(false)).to be(false)
21
+ str = "hello"
22
+ expect(Maybe(str)).to be(str)
23
+ end
24
+
25
+ it 'generates null objects with useful trace info' do
26
+ null = Maybe(); line = __LINE__
27
+ expect(null.__line__).to eq(line)
28
+ expect(null.__file__).to eq(__FILE__)
29
+ end
30
+
31
+ it 'also works with blocks' do
32
+ expect(Maybe{nil}.class).to eq(ConvertableNull)
33
+ expect(Maybe{"foo"}).to eq("foo")
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Null()' do
4
+ include ConvertableNull::Conversions
5
+
6
+ specify 'given no input, returns a null object' do
7
+ expect(Null().class).to be(ConvertableNull)
8
+ end
9
+
10
+ specify 'given nil, returns a null object' do
11
+ expect(Null(nil).class).to be(ConvertableNull)
12
+ end
13
+
14
+ specify 'given a null object, returns the same null object' do
15
+ null = ConvertableNull.get
16
+ expect(Null(null)).to be(null)
17
+ end
18
+
19
+ specify 'given anything in null_equivalents, return a null object' do
20
+ expect(Null("").class).to be(ConvertableNull)
21
+ end
22
+
23
+ specify 'given anything else, raises an ArgumentError' do
24
+ expect{Null(false)}.to raise_error(ArgumentError)
25
+ expect{Null("hello")}.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it 'generates null objects with useful trace info' do
29
+ null = Null(); line = __LINE__
30
+ expect(null.__line__).to eq(line)
31
+ expect(null.__file__).to eq(__FILE__)
32
+ end
33
+
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'implicitly convertable null object' do
4
+ subject(:null) { null_class.new }
5
+ let(:null_class) {
6
+ Naught.build do |b|
7
+ b.define_implicit_conversions
8
+ end
9
+ }
10
+ it 'implicitly splats the same way an empty array does' do
11
+ a, b = null
12
+ expect(a).to be_nil
13
+ expect(b).to be_nil
14
+ end
15
+ it 'is implicitly convertable to String' do
16
+ expect(eval(null)).to be_nil
17
+ end
18
+ it 'implicitly converts to an empty array' do
19
+ expect(null.to_ary).to eq([])
20
+ end
21
+ it 'implicitly converts to an empty string' do
22
+ expect(null.to_str).to eq("")
23
+ end
24
+
25
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+ require 'logger'
3
+
4
+ describe 'null object mimicking a class' do
5
+ class User
6
+ def login; "bob"; end
7
+ end
8
+
9
+ module Authorizable
10
+ def authorized_for?(object); true; end
11
+ end
12
+
13
+ class LibraryPatron < User
14
+ include Authorizable
15
+
16
+ def member?; true; end
17
+ def name; "Bob"; end
18
+ def notify_of_overdue_books(titles); puts 'Notifying...'; end
19
+ end
20
+
21
+ subject(:null) { mimic_class.new }
22
+ let(:mimic_class) {
23
+ Naught.build do |b|
24
+ b.mimic LibraryPatron
25
+ end
26
+ }
27
+ it 'responds to all methods defined on the target class' do
28
+ expect(null.member?).to be_nil
29
+ expect(null.name).to be_nil
30
+ expect(null.notify_of_overdue_books(['The Grapes of Wrath'])).to be_nil
31
+ end
32
+
33
+ it 'does not respond to methods not defined on the target class' do
34
+ expect{null.foobar}.to raise_error(NoMethodError)
35
+ end
36
+
37
+ it 'reports which messages it does and does not respond to' do
38
+ expect(null).to respond_to(:member?)
39
+ expect(null).to respond_to(:name)
40
+ expect(null).to respond_to(:notify_of_overdue_books)
41
+ expect(null).not_to respond_to(:foobar)
42
+ end
43
+ it 'has an informative inspect string' do
44
+ expect(null.inspect).to eq("<null:LibraryPatron>")
45
+ end
46
+
47
+ it 'excludes Object methods from being mimicked' do
48
+ expect(null.object_id).not_to be_nil
49
+ expect(null.hash).not_to be_nil
50
+ end
51
+
52
+ it 'includes inherited methods' do
53
+ expect(null.authorized_for?('something')).to be_nil
54
+ expect(null.login).to be_nil
55
+ end
56
+
57
+ describe 'with include_super: false' do
58
+ let(:mimic_class) {
59
+ Naught.build do |b|
60
+ b.mimic LibraryPatron, include_super: false
61
+ end
62
+ }
63
+
64
+ it 'excludes inherited methods' do
65
+ expect(null).to_not respond_to(:authorized_for?)
66
+ expect(null).to_not respond_to(:login)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'using mimic with black_hole' do
72
+ subject(:null) { mimic_class.new }
73
+ let(:mimic_class) {
74
+ Naught.build do |b|
75
+ b.mimic Logger
76
+ b.black_hole
77
+ end
78
+ }
79
+
80
+ def self.it_behaves_like_a_black_hole_mimic
81
+ it 'returns self from mimicked methods' do
82
+ expect(null.info).to equal(null)
83
+ expect(null.error).to equal(null)
84
+ expect(null << "test").to equal(null)
85
+ end
86
+
87
+ it 'does not respond to methods not defined on the target class' do
88
+ expect{null.foobar}.to raise_error(NoMethodError)
89
+ end
90
+ end
91
+
92
+ it_behaves_like_a_black_hole_mimic
93
+
94
+ describe '(reverse order)' do
95
+ let(:mimic_class) {
96
+ Naught.build do |b|
97
+ b.black_hole
98
+ b.mimic Logger
99
+ end
100
+ }
101
+
102
+ it_behaves_like_a_black_hole_mimic
103
+ end
104
+
105
+ end
106
+
107
+ describe 'mimicking a non-Object-derived class' do
108
+ class MinimalClass < BasicObject
109
+ end
110
+
111
+ subject(:null) { mimic_class.new }
112
+ let(:mimic_class) {
113
+ Naught.build do |b|
114
+ b.mimic MinimalClass
115
+ end
116
+ }
117
+
118
+ it 'generates a BasicObject-derived null class' do
119
+ expect(BasicObject).to be === null
120
+ expect(Object).not_to be === null
121
+ end
122
+ end