naught 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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