method_man 2.1.2 → 3.0.1
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 +5 -5
- data/.rubocop.yml +11 -6
- data/.ruby-version +1 -1
- data/CHANGELOG.md +12 -3
- data/Gemfile +1 -0
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/lib/method_object.rb +74 -113
- data/lib/method_object/version.rb +5 -2
- data/method_man.gemspec +9 -10
- data/spec/method_man_spec.rb +77 -143
- data/spec/spec_helper.rb +1 -0
- metadata +33 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b53cee278d7e41a29dccd2ca0219b34d112d4ac7bd1a070662d23a1c29dde5f3
|
|
4
|
+
data.tar.gz: 483d234c24b309e7ad44391f9aa5cd20626d48182da9dae32857abb618b33629
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6033ad21000327166dd5cfc3601a649fd51cc54db074e83de454fd999bfae106f7bb5e535896c1bdd0d8c8581b3872b81a7babb6e517db0a8f56bbfee1a74696
|
|
7
|
+
data.tar.gz: 33b950b76bc8657b58214e068275ff2f6497536bdb5443a9a853df35caf74076b71ac1774ed1ab5554caac4dc859bb6ad2d54bad971c68874385beba20a92c10
|
data/.rubocop.yml
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
TargetRubyVersion: 2.4
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.5
|
|
5
4
|
|
|
6
5
|
# We don't care about method length, since we check method cyclomatic
|
|
7
6
|
# complexity.
|
|
8
7
|
Metrics/MethodLength:
|
|
9
8
|
Enabled: false
|
|
9
|
+
Metrics/BlockLength:
|
|
10
|
+
Enabled: false
|
|
10
11
|
Metrics/ClassLength:
|
|
11
|
-
|
|
12
|
+
Enabled: false
|
|
12
13
|
Metrics/ModuleLength:
|
|
13
|
-
|
|
14
|
+
Enabled: false
|
|
15
|
+
Metrics/AbcSize:
|
|
16
|
+
Enabled: false
|
|
14
17
|
|
|
15
18
|
# Trailing commas make for clearer diffs because the last line won't appear
|
|
16
19
|
# to have been changed, as it would if it lacked a comma and had one added.
|
|
17
20
|
Style/TrailingCommaInArguments:
|
|
18
21
|
EnforcedStyleForMultiline: comma
|
|
19
|
-
Style/
|
|
22
|
+
Style/TrailingCommaInArrayLiteral:
|
|
23
|
+
EnforcedStyleForMultiline: comma
|
|
24
|
+
Style/TrailingCommaInHashLiteral:
|
|
20
25
|
EnforcedStyleForMultiline: comma
|
|
21
26
|
|
|
22
27
|
# Cop supports --auto-correct.
|
data/.ruby-version
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.7.1
|
|
2
2
|
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
## 2.1.5
|
|
4
|
+
Fix bug where a MethodObject missing attr would raise a nil MethodObject @attributes error instead of method_missing.
|
|
5
|
+
|
|
6
|
+
## 2.1.4
|
|
7
|
+
Fix bug with delegating to assignment methods like `foo=`.
|
|
8
|
+
|
|
9
|
+
## 2.1.1
|
|
10
|
+
Fix bug preventing arguments from being forwarded to delegated methods.
|
|
11
|
+
|
|
12
|
+
## 2.1.0
|
|
13
|
+
Allow automatic delegation inspired by Golang's embedding.
|
|
14
|
+
|
|
3
15
|
## 2.0.0
|
|
4
16
|
Convert MethodObject to use inheritance and a class method for dynamic setup of class internals
|
|
5
17
|
- Enables code editors to find declaration of MethodObject
|
|
6
18
|
- Allows constants to be nested whereas previous implementation was based on a Struct which could not contain constants.
|
|
7
|
-
|
|
8
|
-
## 2.1.0
|
|
9
|
-
Allow automatic delegation inspired by Golang's embedding.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -64,3 +64,33 @@ Also allows automatic delegation inspired by Go's
|
|
|
64
64
|
MakeArbitraryArray.call(company: company)
|
|
65
65
|
=> ['Tyrell Corporation', 'Tyrell Corporation', 'Tyrell Corporation']
|
|
66
66
|
```
|
|
67
|
+
|
|
68
|
+
## Naming
|
|
69
|
+
|
|
70
|
+
Why is the class method called `call`? Some people have argued for names like `TaskSender.send_task` instead of `TaskSender.call`. My reasoning is.
|
|
71
|
+
|
|
72
|
+
1. `call` is a ubiquitous concept in Ruby, as it's how you invoke `Procs`.
|
|
73
|
+
2. There's even a syntactic sugar for this, `.()` instead of `.call()`, e.g. `TaskSender.(task)`.
|
|
74
|
+
3. The name `call` clearly _calls_ out to someone reading the code that "this is an invocation of a method object". I would say this is especially so if you see something like `TaskSender.(task)`.
|
|
75
|
+
4. Avoiding redundancy. Any custom name will always just match the module/class name, e.g.
|
|
76
|
+
```ruby
|
|
77
|
+
TaskSender.send_task # This is redundant
|
|
78
|
+
```
|
|
79
|
+
5. Minimizing complexity: adding an option to specify the class method would introduce additional complexity.
|
|
80
|
+
|
|
81
|
+
### History
|
|
82
|
+
In [Refactoring: Ruby Edition](http://www.informit.com/store/refactoring-ruby-edition-9780321603500), we see this at the top of the section on the method object pattern.
|
|
83
|
+
|
|
84
|
+
> Stolen shamelessly from Kent Beck’s Smalltalk Best Practices.
|
|
85
|
+
> 1. Create a new class, name it after the method.
|
|
86
|
+
|
|
87
|
+
In the example, the instance method `Account#gamma` is refactored to `Gamma.compute`. Kent Beck's original example refactored `Obligation#sendTask` to `TaskSender.compute`.
|
|
88
|
+
|
|
89
|
+
### Noun vs. verb
|
|
90
|
+
|
|
91
|
+
Beck uses `TaskSender`. I personally prefer `SendTask`, essentially just preserving the name of whatever method you're converting to a method object. However I don't think this detail is of much import. I might recommend trying a little of both, and seeing which naming seems least confusing when you're coming back to it weeks later.
|
|
92
|
+
|
|
93
|
+
## How useful is this pattern?
|
|
94
|
+
Kent Beck has [raved about it](https://twitter.com/kentbeck/status/195168291134783489), saying:
|
|
95
|
+
|
|
96
|
+
> extract method object is such deep deep magic. it brings clarity to the confused and structure to the chaotic.
|
data/Rakefile
CHANGED
data/lib/method_object.rb
CHANGED
|
@@ -1,87 +1,95 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'method_object/version'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require('delegate')
|
|
4
|
+
require('method_object/version')
|
|
5
|
+
|
|
6
|
+
# See gemspec for description.
|
|
7
|
+
class MethodObject < SimpleDelegator
|
|
6
8
|
class AmbigousMethodError < NameError; end
|
|
7
9
|
|
|
8
10
|
class << self
|
|
9
|
-
def attrs(*attributes)
|
|
10
|
-
@attributes = attributes
|
|
11
|
-
Setup.call(attributes: attributes, subclass: self)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
11
|
def call(**args)
|
|
15
|
-
new(args).call
|
|
12
|
+
new(__object_factory__&.new(**args)).call
|
|
16
13
|
end
|
|
17
14
|
|
|
18
|
-
attr_reader(:attributes)
|
|
19
|
-
|
|
20
15
|
private(:new)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def initialize(_); end
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def method_missing(name, *args, &block)
|
|
30
|
-
candidates = candidates_for_method_missing(name)
|
|
31
|
-
case candidates.length
|
|
32
|
-
when 0
|
|
33
|
-
super
|
|
34
|
-
when 1
|
|
35
|
-
define_and_call_new_method(candidates.first)
|
|
36
|
-
else
|
|
37
|
-
handle_ambiguous_missing_method(candidates, name)
|
|
17
|
+
def attrs(*attributes)
|
|
18
|
+
self.__object_factory__ = ObjectFactory.create(*attributes)
|
|
38
19
|
end
|
|
20
|
+
|
|
21
|
+
attr_accessor(:__object_factory__)
|
|
39
22
|
end
|
|
40
23
|
|
|
41
|
-
def
|
|
42
|
-
|
|
24
|
+
def call
|
|
25
|
+
raise(NotImplementedError, 'define the call method')
|
|
43
26
|
end
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
end +
|
|
54
|
-
self.class.attributes.map do |attribute|
|
|
55
|
-
PotentialDelegatorWithPrefix.new(
|
|
56
|
-
attribute,
|
|
57
|
-
public_send(attribute),
|
|
58
|
-
method_name,
|
|
59
|
-
)
|
|
28
|
+
# Creates instances for delegation and caching method definitions.
|
|
29
|
+
class ObjectFactory
|
|
30
|
+
STRUCT_DEFINITION = lambda do |_class|
|
|
31
|
+
def method_missing(name, *args)
|
|
32
|
+
candidates = candidates_for_method_missing(name)
|
|
33
|
+
handle_ambiguous_missing_method(candidates, name) if candidates.length > 1
|
|
34
|
+
super
|
|
60
35
|
end
|
|
61
|
-
potential_candidates.select(&:candidate?)
|
|
62
|
-
end
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
37
|
+
def respond_to_missing?(name, _include_private)
|
|
38
|
+
candidates = candidates_for_method_missing(name)
|
|
39
|
+
case candidates.length
|
|
40
|
+
when 0
|
|
41
|
+
return(super)
|
|
42
|
+
when 1
|
|
43
|
+
define_delegated_method(candidates.first)
|
|
69
44
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
public_send(candidate.delegated_method)
|
|
73
|
-
end
|
|
45
|
+
true
|
|
46
|
+
end
|
|
74
47
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
48
|
+
def candidates_for_method_missing(method_name)
|
|
49
|
+
potential_candidates =
|
|
50
|
+
members.map do |attribute|
|
|
51
|
+
PotentialDelegator.new(
|
|
52
|
+
attribute,
|
|
53
|
+
public_send(attribute),
|
|
54
|
+
method_name,
|
|
55
|
+
)
|
|
56
|
+
end +
|
|
57
|
+
members.map do |attribute|
|
|
58
|
+
PotentialDelegatorWithPrefix.new(
|
|
59
|
+
attribute,
|
|
60
|
+
public_send(attribute),
|
|
61
|
+
method_name,
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
potential_candidates.select(&:candidate?)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def define_delegated_method(delegate)
|
|
68
|
+
code = <<~RUBY
|
|
69
|
+
def #{delegate.delegated_method}(*args, &block)
|
|
70
|
+
#{delegate.attribute}
|
|
71
|
+
.#{delegate.method_to_call_on_delegate}(*args, &block)
|
|
72
|
+
end
|
|
73
|
+
RUBY
|
|
74
|
+
self.class.class_eval(code, __FILE__, __LINE__ + 1)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def handle_ambiguous_missing_method(candidates, method_name)
|
|
78
|
+
raise(
|
|
79
|
+
AmbigousMethodError,
|
|
80
|
+
"#{method_name} is ambiguous: " +
|
|
81
|
+
candidates
|
|
82
|
+
.map do |candidate|
|
|
83
|
+
"#{candidate.attribute}.#{candidate.method_to_call_on_delegate}"
|
|
84
|
+
end
|
|
85
|
+
.join(', '),
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.create(*attributes)
|
|
91
|
+
Struct.new(*attributes, keyword_init: true, &STRUCT_DEFINITION)
|
|
92
|
+
end
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
# Represents a possible match of the form:
|
|
@@ -109,58 +117,11 @@ class MethodObject
|
|
|
109
117
|
private
|
|
110
118
|
|
|
111
119
|
def name_matches?
|
|
112
|
-
delegated_method.
|
|
120
|
+
delegated_method.start_with?(prefix)
|
|
113
121
|
end
|
|
114
122
|
|
|
115
123
|
def prefix
|
|
116
124
|
"#{attribute}_"
|
|
117
125
|
end
|
|
118
126
|
end
|
|
119
|
-
|
|
120
|
-
# Dynamically defines custom attr_readers and initializer
|
|
121
|
-
class Setup < SimpleDelegator
|
|
122
|
-
def self.call(attributes:, subclass:)
|
|
123
|
-
new(attributes, subclass).call
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
attr_accessor(:attributes)
|
|
127
|
-
|
|
128
|
-
def initialize(attributes, subclass)
|
|
129
|
-
self.attributes = attributes
|
|
130
|
-
super(subclass)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def call
|
|
134
|
-
define_attr_readers
|
|
135
|
-
define_initializer
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
private
|
|
139
|
-
|
|
140
|
-
def define_attr_readers
|
|
141
|
-
__getobj__.send(:attr_reader, *attributes)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def attr_accessor(attribute)
|
|
145
|
-
super
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def define_initializer
|
|
149
|
-
class_eval(
|
|
150
|
-
<<-RUBY
|
|
151
|
-
def initialize(#{required_keyword_args_string})
|
|
152
|
-
#{assignments}
|
|
153
|
-
end
|
|
154
|
-
RUBY
|
|
155
|
-
)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def required_keyword_args_string
|
|
159
|
-
attributes.map { |arg| "#{arg}:" }.join(', ')
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def assignments
|
|
163
|
-
attributes.map { |attribute| "@#{attribute} = #{attribute}\n" }.join
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
127
|
end
|
data/method_man.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
1
|
# frozen_string_literal: true
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require 'method_object/version'
|
|
6
6
|
|
|
@@ -9,10 +9,8 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.version = MethodObject::VERSION
|
|
10
10
|
spec.authors = ['Clay Shentrup']
|
|
11
11
|
spec.email = %w[cshentrup@gmail.com]
|
|
12
|
-
spec.summary =
|
|
13
|
-
|
|
14
|
-
spec.description = 'Provides a MethodObject class which implements Kent' +
|
|
15
|
-
%q(Beck's "method object" pattern.)
|
|
12
|
+
spec.summary = %(Provides a MethodObject class which implements KentBeck's "method object" pattern.)
|
|
13
|
+
spec.description = %(Provides a MethodObject class which implements KentBeck's "method object" pattern.)
|
|
16
14
|
spec.homepage = 'https://github.com/brokenladder/method_man'
|
|
17
15
|
spec.license = 'MIT'
|
|
18
16
|
|
|
@@ -21,9 +19,10 @@ Gem::Specification.new do |spec|
|
|
|
21
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
22
20
|
spec.require_paths = %w[lib]
|
|
23
21
|
|
|
24
|
-
spec.required_ruby_version = '>= 2.
|
|
22
|
+
spec.required_ruby_version = '>= 2.5'
|
|
25
23
|
|
|
26
|
-
spec.add_development_dependency
|
|
27
|
-
spec.add_development_dependency
|
|
28
|
-
spec.add_development_dependency
|
|
24
|
+
spec.add_development_dependency('bundler')
|
|
25
|
+
spec.add_development_dependency('rake')
|
|
26
|
+
spec.add_development_dependency('rspec')
|
|
27
|
+
spec.add_development_dependency('rubocop')
|
|
29
28
|
end
|
data/spec/method_man_spec.rb
CHANGED
|
@@ -1,185 +1,119 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'method_object'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
expect { subject.new }.to raise_error(NoMethodError)
|
|
7
|
-
end
|
|
3
|
+
require('method_object')
|
|
4
|
+
require('ostruct')
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
RSpec.describe(MethodObject) do
|
|
7
|
+
context('with attrs') do
|
|
8
|
+
let(:method_object) do
|
|
11
9
|
Class.new(described_class) do
|
|
10
|
+
attrs(:block, :attr1, :attr2)
|
|
11
|
+
|
|
12
12
|
def call
|
|
13
|
-
|
|
13
|
+
instance_eval(&block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def local_method
|
|
17
|
+
[attr1, attr2]
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
end
|
|
21
|
+
let(:attr1) { double('attr1', ambiguous_method: nil, delegated_method: delegated_value) }
|
|
22
|
+
let(:delegated_value) { 'delegated value' }
|
|
23
|
+
let(:attr2) do
|
|
24
|
+
double('attr2', ambiguous_method: nil, attr1_ambiguous_method: nil)
|
|
25
|
+
end
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
context 'with attrs' do
|
|
22
|
-
subject do
|
|
23
|
-
Class.new(described_class) do
|
|
24
|
-
attrs(:company, :user)
|
|
25
|
-
|
|
26
|
-
@sent_messages = []
|
|
27
|
-
|
|
28
|
-
def self.sent_messages
|
|
29
|
-
@sent_messages
|
|
30
|
-
end
|
|
27
|
+
def call(&block)
|
|
28
|
+
method_object.call(block: block, attr1: attr1, attr2: attr2)
|
|
29
|
+
end
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
address: address,
|
|
35
|
-
respond_to_address: respond_to_missing?(:address),
|
|
36
|
-
company_address: company_address,
|
|
37
|
-
respond_to_company_address: respond_to_missing?(:company_address),
|
|
38
|
-
company: company,
|
|
39
|
-
respond_to_name: respond_to_missing?(:name),
|
|
40
|
-
company_name: company_name,
|
|
41
|
-
respond_to_company_name: respond_to_missing?(:company_name),
|
|
42
|
-
user: user,
|
|
43
|
-
user_name: user_name,
|
|
44
|
-
respond_to_user_name: respond_to_missing?(:user_name),
|
|
45
|
-
respond_to_missing: respond_to_missing?(:undefined_method),
|
|
46
|
-
}
|
|
47
|
-
end
|
|
48
|
-
end
|
|
31
|
+
it('makes .new a private class method') do
|
|
32
|
+
expect { method_object.new }.to(raise_error(NoMethodError))
|
|
49
33
|
end
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
54
|
-
let(:company_address) { '101 Minitru Lane' }
|
|
55
|
-
let(:company_name) { 'Periscope Data' }
|
|
56
|
-
let(:user) { double('user', name: user_name) }
|
|
57
|
-
let(:user_name) { 'Woody' }
|
|
58
|
-
|
|
59
|
-
let(:result) { subject.call(company: company, user: user) }
|
|
60
|
-
|
|
61
|
-
specify do
|
|
62
|
-
expect(result).to eq(
|
|
63
|
-
address: company_address,
|
|
64
|
-
respond_to_address: true,
|
|
65
|
-
company_address: company_address,
|
|
66
|
-
respond_to_company_address: true,
|
|
67
|
-
company: company,
|
|
68
|
-
respond_to_name: false,
|
|
69
|
-
company_name: company_name,
|
|
70
|
-
respond_to_company_name: true,
|
|
71
|
-
user: user,
|
|
72
|
-
user_name: user_name,
|
|
73
|
-
respond_to_user_name: true,
|
|
74
|
-
respond_to_missing: false,
|
|
75
|
-
)
|
|
35
|
+
it('raises method missing exception for undefined methods') do
|
|
36
|
+
expect { call { undefined_method } }.to(raise_error(NameError, /undefined_method/))
|
|
76
37
|
end
|
|
77
38
|
|
|
78
|
-
it
|
|
79
|
-
expect {
|
|
39
|
+
it('calls its own methods and passed attrs') do
|
|
40
|
+
expect(call { local_method }).to(eq([attr1, attr2]))
|
|
80
41
|
end
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
attrs(:company, :user)
|
|
43
|
+
def delegates?(method)
|
|
44
|
+
call { respond_to?(method) }
|
|
45
|
+
end
|
|
86
46
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
47
|
+
it('delegates to attrs') do
|
|
48
|
+
expect(delegates?(:delegated_method)).to(be(true))
|
|
92
49
|
|
|
93
|
-
|
|
94
|
-
expect { result }.to raise_error(
|
|
95
|
-
MethodObject::AmbigousMethodError,
|
|
96
|
-
a_string_including('company.name, user.name'),
|
|
97
|
-
)
|
|
98
|
-
end
|
|
50
|
+
expect(call { delegated_method }).to(be(delegated_value))
|
|
99
51
|
end
|
|
100
52
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Class.new(described_class) do
|
|
104
|
-
attrs(:company, :user)
|
|
53
|
+
it('delegates to attrs with prefix') do
|
|
54
|
+
expect(delegates?(:attr1_delegated_method)).to(be(true))
|
|
105
55
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
56
|
+
expect(call { attr1_delegated_method }).to(be(delegated_value))
|
|
57
|
+
end
|
|
111
58
|
|
|
112
|
-
|
|
59
|
+
it('raises for ambiguity on delegated method names') do
|
|
60
|
+
expect(delegates?(:ambiguous_method)).to(be(true))
|
|
113
61
|
|
|
114
|
-
|
|
115
|
-
|
|
62
|
+
expect { call { ambiguous_method } }.to(
|
|
63
|
+
raise_error(
|
|
116
64
|
MethodObject::AmbigousMethodError,
|
|
117
|
-
a_string_including('
|
|
118
|
-
)
|
|
119
|
-
|
|
65
|
+
a_string_including('ambiguous_method is ambiguous: attr1.ambiguous_method, attr2.ambiguous_method'),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
120
68
|
end
|
|
121
69
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Class.new(described_class) do
|
|
125
|
-
attrs(:diary)
|
|
70
|
+
it('raises for ambiguity on delegated and prefixed delegated method names') do
|
|
71
|
+
expect(delegates?(:attr1_ambiguous_method)).to(be(true))
|
|
126
72
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
73
|
+
expect { call { attr1_ambiguous_method } }.to(
|
|
74
|
+
raise_error(
|
|
75
|
+
MethodObject::AmbigousMethodError,
|
|
76
|
+
a_string_including(
|
|
77
|
+
'attr1_ambiguous_method is ambiguous: ' \
|
|
78
|
+
'attr2.attr1_ambiguous_method, attr1.ambiguous_method',
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
end
|
|
135
83
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
84
|
+
describe('assignments') do
|
|
85
|
+
let(:attr1) { OpenStruct.new }
|
|
139
86
|
|
|
140
|
-
|
|
141
|
-
|
|
87
|
+
it('assigns') do
|
|
88
|
+
call { attr1.foo = 'bar' }
|
|
89
|
+
expect(attr1.foo).to(eq('bar'))
|
|
142
90
|
end
|
|
143
91
|
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context('without attrs') do
|
|
95
|
+
let(:method_object) do
|
|
96
|
+
Class.new(described_class) do
|
|
97
|
+
def call
|
|
98
|
+
receiver_test
|
|
99
|
+
end
|
|
144
100
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Class.new(described_class) do
|
|
148
|
-
attrs(:company)
|
|
149
|
-
@sent_messages = []
|
|
150
|
-
|
|
151
|
-
def self.sent_messages
|
|
152
|
-
@sent_messages
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def call
|
|
156
|
-
[name, name]
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Ensure it defines resolved methods
|
|
160
|
-
def method_missing(method, *_args)
|
|
161
|
-
if self.class.sent_messages.include?(method)
|
|
162
|
-
raise 'method not memoized'
|
|
163
|
-
end
|
|
164
|
-
self.class.sent_messages << method
|
|
165
|
-
super
|
|
166
|
-
end
|
|
101
|
+
def receiver_test
|
|
102
|
+
42
|
|
167
103
|
end
|
|
168
104
|
end
|
|
105
|
+
end
|
|
169
106
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
end
|
|
107
|
+
def call
|
|
108
|
+
method_object.call
|
|
173
109
|
end
|
|
174
|
-
end
|
|
175
110
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Class.new(described_class) { attrs(:user_1_age) }
|
|
111
|
+
it 'makes new a private class method' do
|
|
112
|
+
expect { method_object.new }.to raise_error(NoMethodError)
|
|
179
113
|
end
|
|
180
114
|
|
|
181
|
-
|
|
182
|
-
expect {
|
|
115
|
+
it('calls itself') do
|
|
116
|
+
expect(call { receiver_test }).to eq(42)
|
|
183
117
|
end
|
|
184
118
|
end
|
|
185
119
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,57 +1,71 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: method_man
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Clay Shentrup
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-09-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
55
69
|
description: Provides a MethodObject class which implements KentBeck's "method object"
|
|
56
70
|
pattern.
|
|
57
71
|
email:
|
|
@@ -79,7 +93,7 @@ homepage: https://github.com/brokenladder/method_man
|
|
|
79
93
|
licenses:
|
|
80
94
|
- MIT
|
|
81
95
|
metadata: {}
|
|
82
|
-
post_install_message:
|
|
96
|
+
post_install_message:
|
|
83
97
|
rdoc_options: []
|
|
84
98
|
require_paths:
|
|
85
99
|
- lib
|
|
@@ -87,16 +101,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
87
101
|
requirements:
|
|
88
102
|
- - ">="
|
|
89
103
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: '2.
|
|
104
|
+
version: '2.5'
|
|
91
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
106
|
requirements:
|
|
93
107
|
- - ">="
|
|
94
108
|
- !ruby/object:Gem::Version
|
|
95
109
|
version: '0'
|
|
96
110
|
requirements: []
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
signing_key:
|
|
111
|
+
rubygems_version: 3.1.2
|
|
112
|
+
signing_key:
|
|
100
113
|
specification_version: 4
|
|
101
114
|
summary: Provides a MethodObject class which implements KentBeck's "method object"
|
|
102
115
|
pattern.
|