mocktail 0.0.6 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67768c396af3e99d7ef7649d2afecf023f1dbdc0e361d960bebbf6befe908d3b
4
- data.tar.gz: 196930b31afa82713bf68469c6d3cb434ce67f22f6cc4c7393c7c438201550c9
3
+ metadata.gz: 05ec28e8027b6d73ee5b3f513ce4d5e23a60ea0266643e344e20e8f995410ce2
4
+ data.tar.gz: 7baf1cff682de031f3f25cebad265e59e598c4f4103968ea2dda61b6c7605c9c
5
5
  SHA512:
6
- metadata.gz: e8b1974f7c8068044c95b4113937aa5cd39972722244ff81bd60038cfe56ff2148a87c8b89bf5487e5b195fa25df1e23f31f667c618e2c375f27615e487d77f7
7
- data.tar.gz: b0cf6ae9e2e1b7b2ce0d23144753d7dde66af999c110da1ac8815b2a95b4bdd6ad0ac1e7555fd4a1333bbc983811fd5fa32a052ea22bda01395a24dfb96eb6da
6
+ metadata.gz: 4c5eef4fe5c68abd7d1e506bc59db58daa931fb484118e0ee4d6d286944d2834d0bb3107183102ac65110e12f32992b73fadbfcc26ed750bff060535e4eba5a2
7
+ data.tar.gz: cff11562ac6bc79475719f17f912ffc2fe8af6315ab90dbdc838143c237542ad788cba2d6c05ee15417799b2f597634244edf3c9f50dac5121adbac551602bdb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 1.0.0
2
+
3
+ * First breaking change! 🎉
4
+ * Remove support for `Mocktail.explain(nil)` because fake nil values cannot be
5
+ made falsey. Pretty big mistake
6
+ * Add `Mocktail.explain_nils` which will return explanation objects of every
7
+ call that didn't satisfy a stubbing since the last reset, including the call
8
+ site where it happened and the backtrace to try to tease out which one you're
9
+ looking for
10
+
1
11
  # 0.0.6
2
12
 
3
13
  * Require pathname, which I missed because `bundle exec` loads it. Wups!
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (0.0.6)
4
+ mocktail (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,29 +10,29 @@ GEM
10
10
  coderay (1.1.3)
11
11
  docile (1.4.0)
12
12
  method_source (1.0.0)
13
- minitest (5.14.4)
13
+ minitest (5.15.0)
14
14
  parallel (1.21.0)
15
- parser (3.0.2.0)
15
+ parser (3.0.3.2)
16
16
  ast (~> 2.4.1)
17
17
  pry (0.14.1)
18
18
  coderay (~> 1.1)
19
19
  method_source (~> 1.0)
20
20
  rainbow (3.0.0)
21
21
  rake (13.0.6)
22
- regexp_parser (2.1.1)
22
+ regexp_parser (2.2.0)
23
23
  rexml (3.2.5)
24
- rubocop (1.20.0)
24
+ rubocop (1.23.0)
25
25
  parallel (~> 1.10)
26
26
  parser (>= 3.0.0.0)
27
27
  rainbow (>= 2.2.2, < 4.0)
28
28
  regexp_parser (>= 1.8, < 3.0)
29
29
  rexml
30
- rubocop-ast (>= 1.9.1, < 2.0)
30
+ rubocop-ast (>= 1.12.0, < 2.0)
31
31
  ruby-progressbar (~> 1.7)
32
32
  unicode-display_width (>= 1.4.0, < 3.0)
33
- rubocop-ast (1.11.0)
33
+ rubocop-ast (1.15.0)
34
34
  parser (>= 3.0.1.1)
35
- rubocop-performance (1.11.5)
35
+ rubocop-performance (1.12.0)
36
36
  rubocop (>= 1.7.0, < 2.0)
37
37
  rubocop-ast (>= 0.4.0)
38
38
  ruby-progressbar (1.11.0)
@@ -42,9 +42,9 @@ GEM
42
42
  simplecov_json_formatter (~> 0.1)
43
43
  simplecov-html (0.12.3)
44
44
  simplecov_json_formatter (0.1.3)
45
- standard (1.3.0)
46
- rubocop (= 1.20.0)
47
- rubocop-performance (= 1.11.5)
45
+ standard (1.5.0)
46
+ rubocop (= 1.23.0)
47
+ rubocop-performance (= 1.12.0)
48
48
  unicode-display_width (2.1.0)
49
49
 
50
50
  PLATFORMS
data/README.md CHANGED
@@ -580,12 +580,27 @@ ice_tray.fill(:water_type, 30)
580
580
  ```
581
581
 
582
582
  From there, you can just copy-paste the provided method stub as a starting point
583
- for your new method.
583
+ for your new method:
584
584
 
585
- #### `nil` values returned by faked methods
585
+ ```ruby
586
+ class IceTray
587
+ def fill(water_type, amount)
588
+ end
589
+ end
590
+ ```
591
+
592
+ ### Unexpected nils with Mocktail.explain_nils
593
+
594
+ Is a faked method returning `nil` and you don't understand why?
595
+
596
+ By default, methods faked by Mocktail will return `nil` when no stubbing is
597
+ satisfied. A frequent frustration, therefore, is when the way `stubs {}.with {}`
598
+ is configured does not satisfy a call the way you expected. To try to make
599
+ debugging this a little bit easier, the gem provides a top-level
600
+ `Mocktail.explain_nils` method that will return an array of summaries of every
601
+ call to a faked method that failed to satisfy any stubbings.
586
602
 
587
- Suppose you go ahead and implement the `fill` method above and configure a
588
- stubbing:
603
+ For example, suppose you stub this `fill` method like so:
589
604
 
590
605
  ```ruby
591
606
  ice_tray = Mocktail.of(IceTray)
@@ -598,20 +613,22 @@ you don't understand why:
598
613
 
599
614
  ```ruby
600
615
  def prep
601
- ice = ice_tray.fill(:tap_water, 50) # => nil
602
- glass.add(ice)
616
+ ice = ice_tray.fill(:tap_water, 50)
617
+ glass.add(ice) # => why is `ice` nil?!
603
618
  end
604
619
  ```
605
620
 
606
- You can pass that `nil` value to `Mocktail.explain` and get an
607
- `UnsatisfiedStubExplanation` that will include both a `reference` object to explore
608
- as well a summary message:
621
+ Whenever you're confused by a nil, you can call `Mocktail.explain_nils` for an
622
+ array containing `UnsatisfyingCallExplanation` objects (one for each call to
623
+ a faked method that did not satisfy any configured stubbings).
624
+
625
+ The returned explanation objects will include both a `reference` object to
626
+ explore as well a summary `message`:
609
627
 
610
628
  ```ruby
611
629
  def prep
612
- ice = ice_tray.fill(:tap_water, 50).tap do |wat|
613
- puts Mocktail.explain(wat).message
614
- end
630
+ ice = ice_tray.fill(:tap_water, 50)
631
+ puts Mocktail.explain_nils.first.message
615
632
  glass.add(ice)
616
633
  end
617
634
  ```
@@ -626,16 +643,24 @@ The actual call:
626
643
 
627
644
  fill(:tap_water, 50)
628
645
 
646
+ The call site:
647
+
648
+ /path/to/your/code.rb:42:in `prep'
649
+
629
650
  Stubbings configured prior to this call but not satisfied by it:
630
651
 
631
652
  fill(:tap_water, 30)
632
653
  ```
633
654
 
655
+ The `reference` object will have details of the `call` itself, an array of
656
+ `other_stubbings` defined on the faked method, and a `backtrace` to determine
657
+ which call site produced the unexpected `nil` value.
658
+
634
659
  #### Fake instances created by Mocktail
635
660
 
636
- Any instances created by `Mocktail.of` or `Mocktail.of_next` can also be passed
637
- to `Mocktail.explain`, and they will list out all the calls and stubbings made
638
- for each of their fake methods.
661
+ Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to
662
+ `Mocktail.explain`, and they will list out all the calls and stubbings made for
663
+ each of their fake methods.
639
664
 
640
665
  Calling `Mocktail.explain(ice_tray).message` following the example above will
641
666
  yield:
@@ -662,6 +687,8 @@ passed to `Mocktail.explain()` for a summary of all the stubbing configurations
662
687
  and calls made against its faked singleton methods for the currently running
663
688
  thread.
664
689
 
690
+ Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods:
691
+
665
692
  ```ruby
666
693
  Mocktail.replace(Shop)
667
694
 
@@ -705,6 +732,10 @@ but you _definitely_ want to call it if you're using `Mocktail.replace` or
705
732
  `Mocktail.of_next` anywhere, since those will affect state that is shared across
706
733
  tests.
707
734
 
735
+ Calling reset in a `teardown` or `after(:each)` hook will also improve the
736
+ usefulness of messages returned by `Mocktail.explain` and
737
+ `Mocktail.explain_nils`.
738
+
708
739
  ## Acknowledgements
709
740
 
710
741
  Mocktail is created & maintained by the software agency [Test
@@ -0,0 +1,35 @@
1
+ require_relative "share/stringifies_method_name"
2
+ require_relative "share/stringifies_call"
3
+
4
+ module Mocktail
5
+ class ExplainsNils
6
+ def initialize
7
+ @stringifies_method_name = StringifiesMethodName.new
8
+ @stringifies_call = StringifiesCall.new
9
+ end
10
+
11
+ def explain
12
+ Mocktail.cabinet.unsatisfying_calls.map { |unsatisfying_call|
13
+ dry_call = unsatisfying_call.call
14
+ other_stubbings = unsatisfying_call.other_stubbings
15
+
16
+ UnsatisfyingCallExplanation.new(unsatisfying_call, <<~MSG)
17
+ `nil' was returned by a mocked `#{@stringifies_method_name.stringify(dry_call)}' method
18
+ because none of its configured stubbings were satisfied.
19
+
20
+ The actual call:
21
+
22
+ #{@stringifies_call.stringify(dry_call, always_parens: true)}
23
+
24
+ The call site:
25
+
26
+ #{unsatisfying_call.backtrace.first}
27
+
28
+ #{@stringifies_call.stringify_multiple(other_stubbings.map(&:recording),
29
+ nonzero_message: "Stubbings configured prior to this call but not satisfied by it",
30
+ zero_message: "No stubbings were configured on this method")}
31
+ MSG
32
+ }
33
+ end
34
+ end
35
+ end
@@ -9,9 +9,7 @@ module Mocktail
9
9
  end
10
10
 
11
11
  def explain(thing)
12
- if is_stub_returned_nil?(thing)
13
- unsatisfied_stub_explanation(thing)
14
- elsif (double = Mocktail.cabinet.double_for_instance(thing))
12
+ if (double = Mocktail.cabinet.double_for_instance(thing))
15
13
  double_explanation(double)
16
14
  elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
17
15
  replaced_type_explanation(type_replacement)
@@ -22,31 +20,6 @@ module Mocktail
22
20
 
23
21
  private
24
22
 
25
- # Our fake nil doesn't even implement respond_to?, instead quacking like nil
26
- def is_stub_returned_nil?(thing)
27
- thing.was_returned_by_unsatisfied_stub?
28
- rescue NoMethodError
29
- end
30
-
31
- def unsatisfied_stub_explanation(stub_returned_nil)
32
- unsatisfied_stubbing = stub_returned_nil.unsatisfied_stubbing
33
- dry_call = unsatisfied_stubbing.call
34
- other_stubbings = unsatisfied_stubbing.other_stubbings
35
-
36
- UnsatisfiedStubExplanation.new(unsatisfied_stubbing, <<~MSG)
37
- This `nil' was returned by a mocked `#{@stringifies_method_name.stringify(dry_call)}' method
38
- because none of its configured stubbings were satisfied.
39
-
40
- The actual call:
41
-
42
- #{@stringifies_call.stringify(dry_call, always_parens: true)}
43
-
44
- #{describe_multiple_calls(other_stubbings.map(&:recording),
45
- "Stubbings configured prior to this call but not satisfied by it",
46
- "No stubbings were configured on this method")}
47
- MSG
48
- end
49
-
50
23
  def double_explanation(double)
51
24
  double_data = DoubleData.new(
52
25
  type: double.original_type,
@@ -95,35 +68,23 @@ module Mocktail
95
68
  ))
96
69
 
97
70
  [
98
- describe_multiple_calls(
71
+ @stringifies_call.stringify_multiple(
99
72
  double_data.stubbings.map(&:recording).select { |call|
100
73
  call.method == method
101
74
  },
102
- "`#{method_name}' stubbings",
103
- "`#{method_name}' has no stubbings"
75
+ nonzero_message: "`#{method_name}' stubbings",
76
+ zero_message: "`#{method_name}' has no stubbings"
104
77
  ),
105
- describe_multiple_calls(
78
+ @stringifies_call.stringify_multiple(
106
79
  double_data.calls.select { |call|
107
80
  call.method == method
108
81
  },
109
- "`#{method_name}' calls",
110
- "`#{method_name}' has no calls"
82
+ nonzero_message: "`#{method_name}' calls",
83
+ zero_message: "`#{method_name}' has no calls"
111
84
  )
112
85
  ].join("\n")
113
86
  end
114
87
 
115
- def describe_multiple_calls(calls, nonzero_message, zero_message)
116
- if calls.empty?
117
- "#{zero_message}.\n"
118
- else
119
- <<~MSG
120
- #{nonzero_message}:
121
-
122
- #{calls.map { |call| " " + @stringifies_call.stringify(call) }.join("\n\n")}
123
- MSG
124
- end
125
- end
126
-
127
88
  def no_explanation(thing)
128
89
  NoExplanation.new(thing,
129
90
  "Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
@@ -1,12 +1,19 @@
1
+ require_relative "../../share/cleans_backtrace"
2
+
1
3
  module Mocktail
2
4
  class DescribesUnsatisfiedStubbing
5
+ def initialize
6
+ @cleans_backtrace = CleansBacktrace.new
7
+ end
8
+
3
9
  def describe(dry_call)
4
- UnsatisfiedStubbing.new(
10
+ UnsatisfyingCall.new(
5
11
  call: dry_call,
6
12
  other_stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
7
- dry_call.double == stubbing.recording.double &&
8
- dry_call.method == stubbing.recording.method
9
- }
13
+ dry_call.double == stubbing.recording.double &&
14
+ dry_call.method == stubbing.recording.method
15
+ },
16
+ backtrace: @cleans_backtrace.clean(Error.new).backtrace
10
17
  )
11
18
  end
12
19
  end
@@ -13,13 +13,25 @@ module Mocktail
13
13
  stubbing.satisfied!
14
14
  stubbing.effect&.call(dry_call)
15
15
  else
16
- StubReturnedNil.new(@describes_unsatisfied_stubbing.describe(dry_call))
16
+ store_unsatisfying_call!(dry_call)
17
+ nil
17
18
  end
18
19
  end
19
20
 
20
21
  def satisfaction(dry_call)
21
22
  return if Mocktail.cabinet.demonstration_in_progress?
23
+
22
24
  @finds_satisfaction.find(dry_call)
23
25
  end
26
+
27
+ private
28
+
29
+ def store_unsatisfying_call!(dry_call)
30
+ return if Mocktail.cabinet.demonstration_in_progress?
31
+
32
+ Mocktail.cabinet.store_unsatisfying_call(
33
+ @describes_unsatisfied_stubbing.describe(dry_call)
34
+ )
35
+ end
24
36
  end
25
37
  end
@@ -34,6 +34,10 @@ module Mocktail::Matchers
34
34
  def captured?
35
35
  @captured
36
36
  end
37
+
38
+ def inspect
39
+ "capture"
40
+ end
37
41
  end
38
42
 
39
43
  attr_reader :capture
@@ -6,7 +6,7 @@ module Mocktail::Matchers
6
6
 
7
7
  def initialize(&blk)
8
8
  if blk.nil?
9
- raise "The `that` matcher must be passed a block (e.g. `that { |arg| … }`)"
9
+ raise ArgumentError.new("The `that` matcher must be passed a block (e.g. `that { |arg| … }`)")
10
10
  end
11
11
  @blk = blk
12
12
  end
@@ -4,6 +4,20 @@ module Mocktail
4
4
  "#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
5
5
  end
6
6
 
7
+ def stringify_multiple(calls, nonzero_message:, zero_message:,
8
+ anonymous_blocks: false, always_parens: false)
9
+
10
+ if calls.empty?
11
+ "#{zero_message}.\n"
12
+ else
13
+ <<~MSG
14
+ #{nonzero_message}:
15
+
16
+ #{calls.map { |call| " " + stringify(call) }.join("\n\n")}
17
+ MSG
18
+ end
19
+ end
20
+
7
21
  private
8
22
 
9
23
  def args_to_s(call, parens: true)
@@ -1,7 +1,7 @@
1
1
  require_relative "simulates_argument_error/transforms_params"
2
2
  require_relative "simulates_argument_error/reconciles_args_with_params"
3
3
  require_relative "simulates_argument_error/recreates_message"
4
- require_relative "simulates_argument_error/cleans_backtrace"
4
+ require_relative "share/cleans_backtrace"
5
5
  require_relative "share/stringifies_call"
6
6
 
7
7
  module Mocktail
@@ -3,18 +3,20 @@
3
3
  module Mocktail
4
4
  class Cabinet
5
5
  attr_writer :demonstration_in_progress
6
- attr_reader :calls, :stubbings
6
+ attr_reader :calls, :stubbings, :unsatisfying_calls
7
7
 
8
8
  def initialize
9
9
  @doubles = []
10
10
  @calls = []
11
11
  @stubbings = []
12
+ @unsatisfying_calls = []
12
13
  @demonstration_in_progress = false
13
14
  end
14
15
 
15
16
  def reset!
16
17
  @calls = []
17
18
  @stubbings = []
19
+ @unsatisfying_calls = []
18
20
  # Could cause an exception or prevent pollution—you decide!
19
21
  @demonstration_in_progress = false
20
22
  # note we don't reset doubles as they don't carry any
@@ -34,6 +36,10 @@ module Mocktail
34
36
  @stubbings << stubbing
35
37
  end
36
38
 
39
+ def store_unsatisfying_call(unsatisfying_call)
40
+ @unsatisfying_calls << unsatisfying_call
41
+ end
42
+
37
43
  def demonstration_in_progress?
38
44
  @demonstration_in_progress
39
45
  end
@@ -15,7 +15,7 @@ module Mocktail
15
15
  class NoExplanation < Explanation
16
16
  end
17
17
 
18
- class UnsatisfiedStubExplanation < Explanation
18
+ class UnsatisfyingCallExplanation < Explanation
19
19
  end
20
20
 
21
21
  class DoubleExplanation < Explanation
@@ -1,7 +1,8 @@
1
1
  module Mocktail
2
- class UnsatisfiedStubbing < Struct.new(
2
+ class UnsatisfyingCall < Struct.new(
3
3
  :call,
4
4
  :other_stubbings,
5
+ :backtrace,
5
6
  keyword_init: true
6
7
  )
7
8
  end
@@ -7,8 +7,7 @@ require_relative "value/explanation"
7
7
  require_relative "value/matcher_registry"
8
8
  require_relative "value/signature"
9
9
  require_relative "value/stubbing"
10
- require_relative "value/stub_returned_nil"
11
10
  require_relative "value/top_shelf"
12
11
  require_relative "value/type_replacement"
13
12
  require_relative "value/type_replacement_data"
14
- require_relative "value/unsatisfied_stubbing"
13
+ require_relative "value/unsatisfying_call"
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "0.0.6"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/mocktail.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require_relative "mocktail/dsl"
2
2
  require_relative "mocktail/errors"
3
3
  require_relative "mocktail/explains_thing"
4
+ require_relative "mocktail/explains_nils"
4
5
  require_relative "mocktail/handles_dry_call"
5
6
  require_relative "mocktail/handles_dry_new_call"
6
7
  require_relative "mocktail/imitates_type"
@@ -61,6 +62,10 @@ module Mocktail
61
62
  ExplainsThing.new.explain(thing)
62
63
  end
63
64
 
65
+ def self.explain_nils
66
+ ExplainsNils.new.explain
67
+ end
68
+
64
69
  # Stores most transactional state about calls & stubbing configurations
65
70
  # Anything returned by this is undocumented and could change at any time, so
66
71
  # don't commit code that relies on it!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mocktail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-04 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -31,6 +31,7 @@ files:
31
31
  - lib/mocktail.rb
32
32
  - lib/mocktail/dsl.rb
33
33
  - lib/mocktail/errors.rb
34
+ - lib/mocktail/explains_nils.rb
34
35
  - lib/mocktail/explains_thing.rb
35
36
  - lib/mocktail/handles_dry_call.rb
36
37
  - lib/mocktail/handles_dry_call/fulfills_stubbing.rb
@@ -65,12 +66,12 @@ files:
65
66
  - lib/mocktail/replaces_type/redefines_new.rb
66
67
  - lib/mocktail/replaces_type/redefines_singleton_methods.rb
67
68
  - lib/mocktail/resets_state.rb
69
+ - lib/mocktail/share/cleans_backtrace.rb
68
70
  - lib/mocktail/share/creates_identifier.rb
69
71
  - lib/mocktail/share/determines_matching_calls.rb
70
72
  - lib/mocktail/share/stringifies_call.rb
71
73
  - lib/mocktail/share/stringifies_method_name.rb
72
74
  - lib/mocktail/simulates_argument_error.rb
73
- - lib/mocktail/simulates_argument_error/cleans_backtrace.rb
74
75
  - lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb
75
76
  - lib/mocktail/simulates_argument_error/recreates_message.rb
76
77
  - lib/mocktail/simulates_argument_error/transforms_params.rb
@@ -83,12 +84,11 @@ files:
83
84
  - lib/mocktail/value/explanation.rb
84
85
  - lib/mocktail/value/matcher_registry.rb
85
86
  - lib/mocktail/value/signature.rb
86
- - lib/mocktail/value/stub_returned_nil.rb
87
87
  - lib/mocktail/value/stubbing.rb
88
88
  - lib/mocktail/value/top_shelf.rb
89
89
  - lib/mocktail/value/type_replacement.rb
90
90
  - lib/mocktail/value/type_replacement_data.rb
91
- - lib/mocktail/value/unsatisfied_stubbing.rb
91
+ - lib/mocktail/value/unsatisfying_call.rb
92
92
  - lib/mocktail/verifies_call.rb
93
93
  - lib/mocktail/verifies_call/finds_verifiable_calls.rb
94
94
  - lib/mocktail/verifies_call/raises_verification_error.rb
@@ -1,26 +0,0 @@
1
- module Mocktail
2
- class StubReturnedNil < BasicObject
3
- attr_reader :unsatisfied_stubbing
4
-
5
- def initialize(unsatisfied_stubbing)
6
- @unsatisfied_stubbing = unsatisfied_stubbing
7
- end
8
-
9
- def was_returned_by_unsatisfied_stub?
10
- true
11
- end
12
-
13
- def tap
14
- yield self
15
- self
16
- end
17
-
18
- def method_missing(name, *args, **kwargs, &blk)
19
- nil.send(name, *args, **kwargs, &blk)
20
- end
21
-
22
- def respond_to_missing?(name, include_all = false)
23
- nil.respond_to?(name, include_all)
24
- end
25
- end
26
- end