casting 0.7.1 → 1.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/LICENSE +1 -1
- data/README.md +18 -21
- data/lib/casting/client.rb +4 -4
- data/lib/casting/context.rb +136 -0
- data/lib/casting/delegation.rb +87 -15
- data/lib/casting/enum.rb +11 -0
- data/lib/casting/missing_method_client.rb +6 -4
- data/lib/casting/missing_method_client_class.rb +1 -1
- data/lib/casting/null.rb +7 -7
- data/lib/casting/super_delegate.rb +24 -18
- data/lib/casting/version.rb +2 -2
- data/lib/casting.rb +3 -0
- data/test/casting_enum_test.rb +40 -0
- data/test/casting_test.rb +19 -0
- data/test/client_test.rb +1 -1
- data/test/context_test.rb +80 -0
- data/test/delegation_test.rb +143 -9
- data/test/frozen_client_test.rb +14 -0
- data/test/null_module_test.rb +11 -0
- data/test/super_test.rb +53 -4
- data/test/test_helper.rb +21 -7
- metadata +23 -21
- data/lib/casting/prepared_delegation.rb +0 -76
- data/test/casting_19_test.rb +0 -54
- data/test/casting_20_test.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 87f6f44b2f7597c28ab23eb2d02fd06479558ac075f3b6646cd583ea98e5f945
|
4
|
+
data.tar.gz: cdf55addbcdbaf848d4991fe8562b5bbeb50ea9b3fde1548f210ebb76d761186
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71d6eac4b57b571f8c593606f645477402dd9557081ac684c9c5117257e111916b17ec830836cfe9063acae7572aef5b96e20d5219558bf466769119a95b42a1
|
7
|
+
data.tar.gz: b81bf24dc30d94cbb520eb302f5173efe2f64b19b062a128071c30833df2f92c4a643b47717fa3092afdc2f20cc15a78631b2fa0fab1a80a8e6e9ed18c7bddc9
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# Casting
|
2
2
|
|
3
|
-
[](https://travis-ci.org/saturnflyer/casting)
|
4
3
|
[](https://codeclimate.com/github/saturnflyer/casting)
|
5
|
-
[](https://codeclimate.com/github/saturnflyer/casting/coverage)
|
6
5
|
[](http://badge.fury.io/rb/casting)
|
7
6
|
|
8
7
|
## Add behavior to your objects without using extend
|
@@ -18,7 +17,7 @@ Here's a quick example that you might try in a Rails project:
|
|
18
17
|
# implement a module that contains information for the request response
|
19
18
|
# and apply it to an object in your system.
|
20
19
|
def show
|
21
|
-
|
20
|
+
@user = user.cast_as(UserRepresenter)
|
22
21
|
end
|
23
22
|
```
|
24
23
|
|
@@ -72,6 +71,12 @@ You may also delegate methods without an explicit attendant instance, but provid
|
|
72
71
|
a module containing the behavior you need to use:
|
73
72
|
|
74
73
|
```ruby
|
74
|
+
module GreetingModule
|
75
|
+
def hello_world
|
76
|
+
"hello world"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
75
80
|
actor.delegate(:hello_world, GreetingModule)
|
76
81
|
# or
|
77
82
|
actor.delegation(:hello_world).to(GreetingModule).call
|
@@ -98,6 +103,12 @@ Casting also provides an option to temporarily apply behaviors to an object.
|
|
98
103
|
Once your class or object is a `Casting::Client` you may send the `delegate_missing_methods` message to it and your object will use `method_missing` to delegate methods to a stored attendant.
|
99
104
|
|
100
105
|
```ruby
|
106
|
+
class Actor
|
107
|
+
include Casting::Client
|
108
|
+
delegate_missing_methods
|
109
|
+
end
|
110
|
+
actor = Actor.new
|
111
|
+
|
101
112
|
actor.hello_world #=> NoMethodError
|
102
113
|
|
103
114
|
Casting.delegating(actor => GreetingModule) do
|
@@ -109,7 +120,7 @@ actor.hello_world #=> NoMethodError
|
|
109
120
|
|
110
121
|
The use of `method_missing` is opt-in. If you don't want that mucking up your method calls, just don't tell it to `delegate_missing_methods`.
|
111
122
|
|
112
|
-
Before the block is run in `Casting.delegating`, a collection of delegate objects is set
|
123
|
+
Before the block is run in `Casting.delegating`, a collection of delegate objects is set in the current Thread for the provided attendant. Then the block yields, and an `ensure` block cleans up the stored attendant.
|
113
124
|
|
114
125
|
This allows you to nest your `delegating` blocks as well:
|
115
126
|
|
@@ -293,22 +304,14 @@ Person.instance_method(:hello) #=> #<UnboundMethod: Person#hello>
|
|
293
304
|
But if you attempt to use that `UnboundMethod` on an object that is not a `Person` you'll get
|
294
305
|
an error about a type mismatch.
|
295
306
|
|
296
|
-
Casting will bind an
|
307
|
+
Casting will bind an UnboundMethod method to a client object and execute the method as though it is
|
297
308
|
defined on the client object. Any reference to `self` from the method block will refer to the
|
298
309
|
client object.
|
299
310
|
|
300
|
-
|
301
|
-
|
302
|
-
According to [http://rubyspec.org](http://rubyspec.org) the behavior in MRI in 1.9 that allows this to happen is incorrect. In MRI (and JRuby) 1.9 you may unbind methods from an object that has been extended with a module, and bind them to another object of the same type that has *not* been extended with that module.
|
303
|
-
|
304
|
-
Casting uses this as a way to trick the interpreter into using the method where we want it and avoid forever extending the object of concern.
|
305
|
-
|
306
|
-
This changed in Ruby 2.0 and does not work. What does work (and is so much better) in 2.0 is that you may take any method from a module and apply it to any object. This means that Casting doesn't have to perform any tricks to temporarily apply behavior to an object.
|
307
|
-
|
308
|
-
For example, this fails in 1.9, but works in 2.0:
|
311
|
+
Rather than define methods on classes, you may take any method from a module and apply it to any object regardless of its class.
|
309
312
|
|
310
313
|
```ruby
|
311
|
-
GreetingModule.instance_method(:
|
314
|
+
GreetingModule.instance_method(:hello).bind(actor).call
|
312
315
|
```
|
313
316
|
|
314
317
|
Casting provides a convenience for doing this.
|
@@ -350,12 +353,6 @@ If you are using Bundler, add this line to your application's Gemfile:
|
|
350
353
|
gem 'casting'
|
351
354
|
```
|
352
355
|
|
353
|
-
If you're using Ruby 1.9, be sure to use a version lower than 0.7
|
354
|
-
|
355
|
-
```ruby
|
356
|
-
gem 'casting', '~> 0.6.9'
|
357
|
-
```
|
358
|
-
|
359
356
|
And then execute:
|
360
357
|
|
361
358
|
$ bundle
|
data/lib/casting/client.rb
CHANGED
@@ -22,12 +22,12 @@ module Casting
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def delegation(delegated_method_name)
|
25
|
-
Casting::Delegation.
|
25
|
+
Casting::Delegation.prepare(delegated_method_name, self)
|
26
26
|
end
|
27
27
|
|
28
|
-
def cast(delegated_method_name, attendant,
|
28
|
+
def cast(delegated_method_name, attendant, ...)
|
29
29
|
validate_attendant(attendant)
|
30
|
-
delegation(delegated_method_name).to(attendant).
|
30
|
+
delegation(delegated_method_name).to(attendant).call(...)
|
31
31
|
end
|
32
32
|
|
33
33
|
def delegate_missing_methods(*which)
|
@@ -63,4 +63,4 @@ module Casting
|
|
63
63
|
base.send(:extend, ::Casting::MissingMethodClientClass)
|
64
64
|
end
|
65
65
|
end
|
66
|
-
end
|
66
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# This is an experimental implementation of allowing contextual use of behaviors.
|
2
|
+
#
|
3
|
+
# This relies on versions of Ruby supporting refinements.
|
4
|
+
#
|
5
|
+
# You *must* include the following module and use it for refinements, and
|
6
|
+
# you *must* set the current context for the thread:
|
7
|
+
#
|
8
|
+
# class SomeContext
|
9
|
+
# using Casting::Context
|
10
|
+
# extend Casting::Context
|
11
|
+
#
|
12
|
+
# initialize(:some, :thing)
|
13
|
+
# # doing that defines your constructor but would cause it too look for
|
14
|
+
# # modules named Some and Thing
|
15
|
+
# module Some; end
|
16
|
+
# module Thing; end
|
17
|
+
#
|
18
|
+
# # if you want different module names (why would you?) then you'd need
|
19
|
+
# # to do all this:
|
20
|
+
# def initialize(some, thing)
|
21
|
+
# assign [some, SomeRole], [thing, OtherRole]
|
22
|
+
# Thread.current[:context] = self
|
23
|
+
# end
|
24
|
+
# attr_reader :some, :thing, :assignments
|
25
|
+
#
|
26
|
+
# module SomeRole; end
|
27
|
+
# module OtherRole; end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
module Casting
|
31
|
+
module Context
|
32
|
+
|
33
|
+
def self.extended(base)
|
34
|
+
base.send(:include, InstanceMethods)
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(*setup_args, &block)
|
38
|
+
attr_reader(*setup_args)
|
39
|
+
private(*setup_args)
|
40
|
+
|
41
|
+
if block
|
42
|
+
define_method(:__custom_initialize, &block)
|
43
|
+
else
|
44
|
+
define_method(:__custom_initialize) do; end
|
45
|
+
end
|
46
|
+
|
47
|
+
mod = Module.new
|
48
|
+
line = __LINE__; string = %<
|
49
|
+
def initialize(#{setup_args.map{|name| "#{name}:" }.join(',')})
|
50
|
+
@assignments = []
|
51
|
+
#{setup_args.map do |name|
|
52
|
+
["assign(",name,", '",name,"')"].join
|
53
|
+
end.join("\n")}
|
54
|
+
__custom_initialize
|
55
|
+
Thread.current[:context] = self
|
56
|
+
end
|
57
|
+
attr_reader :assignments
|
58
|
+
>
|
59
|
+
mod.class_eval string, __FILE__, line
|
60
|
+
const_set('Initializer', mod)
|
61
|
+
include mod
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods
|
65
|
+
def context
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def assignments
|
70
|
+
@assignments ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
# Keep track of objects and their behaviors
|
74
|
+
def assign(object, role_name)
|
75
|
+
instance_variable_set("@#{role_name}", object)
|
76
|
+
self.assignments << [object, self.role_for(role_name)]
|
77
|
+
end
|
78
|
+
|
79
|
+
def contains?(obj)
|
80
|
+
assignments.map(&:first).include?(obj)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Execute the behavior from the role on the specifed object
|
84
|
+
def dispatch(object, method_name, ...)
|
85
|
+
if object.respond_to?(:cast)
|
86
|
+
object.cast(method_name, context.role_implementing(object, method_name), ...)
|
87
|
+
else
|
88
|
+
Casting::Delegation.prepare(method_name, object).to(role_implementing(object, method_name)).with(...).call
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Find the first assigned role which implements a response for the given method name
|
93
|
+
def role_implementing(object, method_name)
|
94
|
+
assigned_roles(object).find{|role| role.method_defined?(method_name) } || raise(NoMethodError, "unknown method '#{method_name}' expected for #{object}")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get the roles for the given object
|
98
|
+
def assigned_roles(object)
|
99
|
+
assignments.select{|pair|
|
100
|
+
pair.first == object
|
101
|
+
}.map(&:last)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get the behavior module for the named role.
|
105
|
+
# This role constant for special_person is SpecialPerson.
|
106
|
+
def role_for(name)
|
107
|
+
role_name = name.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }
|
108
|
+
self.class.const_get(role_name)
|
109
|
+
rescue NameError
|
110
|
+
Module.new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
refine Object do
|
115
|
+
def context
|
116
|
+
Thread.current[:context]
|
117
|
+
end
|
118
|
+
|
119
|
+
def context=(obj)
|
120
|
+
Thread.current[:context] = obj
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get the object playing a particular role
|
124
|
+
def role(role_name)
|
125
|
+
context.send(role_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Execute the named method on the object plaing the name role
|
129
|
+
def tell(role_name, method_name, ...)
|
130
|
+
if context == self || context.contains?(self)
|
131
|
+
context.dispatch(role(role_name), method_name, ...)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/casting/delegation.rb
CHANGED
@@ -1,32 +1,104 @@
|
|
1
|
-
require 'casting/prepared_delegation'
|
2
|
-
|
3
1
|
module Casting
|
4
|
-
class Delegation
|
5
2
|
|
6
|
-
|
7
|
-
|
3
|
+
class MissingAttendant < StandardError
|
4
|
+
def message
|
5
|
+
"You must set your attendant object using `to'."
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidAttendant < StandardError; end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
+
class Delegation
|
12
|
+
|
13
|
+
def self.prepare(delegated_method_name, client, &block)
|
14
|
+
new(delegated_method_name: delegated_method_name, client: client, &block)
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
attr_accessor :client, :delegated_method_name, :attendant, :arguments, :block
|
18
|
+
private :block
|
19
|
+
|
20
|
+
def initialize(**settings, &block)
|
21
|
+
@delegated_method_name = settings[:delegated_method_name]
|
22
|
+
@client = settings[:client]
|
23
|
+
@attendant = settings[:attendant]
|
24
|
+
@arguments = settings[:arguments]
|
25
|
+
@keyword_arguments = settings[:keyword_arguments]
|
26
|
+
@block = block
|
15
27
|
end
|
16
28
|
|
17
29
|
def to(object_or_module)
|
18
|
-
|
30
|
+
@attendant = object_or_module
|
31
|
+
begin
|
32
|
+
bound_method
|
33
|
+
rescue TypeError
|
34
|
+
@attendant = method_module || raise
|
35
|
+
end
|
19
36
|
self
|
20
37
|
end
|
21
38
|
|
22
|
-
def with(*args, &block)
|
23
|
-
|
39
|
+
def with(*args, **kwargs, &block)
|
40
|
+
@arguments = args
|
41
|
+
@keyword_arguments = kwargs
|
42
|
+
@block = block
|
24
43
|
self
|
25
44
|
end
|
26
45
|
|
27
|
-
def call(*args)
|
28
|
-
|
46
|
+
def call(*args, **kwargs, &block)
|
47
|
+
raise MissingAttendant.new unless attendant
|
48
|
+
|
49
|
+
call_args = if args && !args.empty?
|
50
|
+
args
|
51
|
+
elsif @arguments && !@arguments.empty?
|
52
|
+
@arguments
|
53
|
+
end
|
54
|
+
call_kwargs = if kwargs && !kwargs.empty?
|
55
|
+
kwargs
|
56
|
+
elsif @keyword_arguments && !@keyword_arguments.empty?
|
57
|
+
@keyword_arguments
|
58
|
+
end
|
59
|
+
|
60
|
+
call_block = block || @block
|
61
|
+
if call_args
|
62
|
+
if call_kwargs
|
63
|
+
bound_method.call(*call_args, **call_kwargs, &call_block)
|
64
|
+
else
|
65
|
+
bound_method.call(*call_args, &call_block)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
if call_kwargs
|
69
|
+
bound_method.call(**call_kwargs, &call_block)
|
70
|
+
else
|
71
|
+
bound_method.call(&call_block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def bound_method
|
79
|
+
begin
|
80
|
+
delegated_method.bind(client)
|
81
|
+
rescue TypeError
|
82
|
+
raise TypeError.new("`to' argument must be a module or an object with #{delegated_method_name} defined in a module")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_module
|
87
|
+
mod = delegated_method.owner
|
88
|
+
unless mod.is_a?(Class)
|
89
|
+
mod
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def delegated_method
|
94
|
+
if Module === attendant
|
95
|
+
attendant.instance_method(delegated_method_name)
|
96
|
+
else
|
97
|
+
attendant.method(delegated_method_name).owner.instance_method(delegated_method_name)
|
98
|
+
end
|
99
|
+
rescue NameError => e
|
100
|
+
raise InvalidAttendant.new(e.message)
|
29
101
|
end
|
30
102
|
|
31
103
|
end
|
32
|
-
end
|
104
|
+
end
|
data/lib/casting/enum.rb
ADDED
@@ -47,13 +47,15 @@ module Casting
|
|
47
47
|
private
|
48
48
|
|
49
49
|
def __delegates__
|
50
|
-
|
50
|
+
Thread.current[:instance_delegates] ||= {}
|
51
|
+
Thread.current[:instance_delegates][object_id] ||= []
|
52
|
+
Thread.current[:instance_delegates][object_id]
|
51
53
|
end
|
52
54
|
|
53
|
-
def method_missing(meth,
|
55
|
+
def method_missing(meth, ...)
|
54
56
|
attendant = method_delegate(meth)
|
55
57
|
if !!attendant
|
56
|
-
|
58
|
+
cast(meth, attendant, ...)
|
57
59
|
else
|
58
60
|
super
|
59
61
|
end
|
@@ -100,4 +102,4 @@ module Casting
|
|
100
102
|
end
|
101
103
|
end
|
102
104
|
end
|
103
|
-
end
|
105
|
+
end
|
data/lib/casting/null.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
module Casting
|
2
2
|
module Null
|
3
|
-
def self.instance_method(
|
3
|
+
def self.instance_method(*_)
|
4
4
|
Empty.instance_method(:null)
|
5
5
|
end
|
6
|
-
def self.method_defined?(
|
6
|
+
def self.method_defined?(*_)
|
7
7
|
true
|
8
8
|
end
|
9
9
|
end
|
10
10
|
module Blank
|
11
|
-
def self.instance_method(
|
11
|
+
def self.instance_method(*_)
|
12
12
|
Empty.instance_method(:blank)
|
13
13
|
end
|
14
|
-
def self.method_defined?(
|
14
|
+
def self.method_defined?(*_)
|
15
15
|
true
|
16
16
|
end
|
17
17
|
end
|
18
18
|
module Empty
|
19
|
-
def null(
|
20
|
-
def blank(
|
19
|
+
def null(*, &_block); end
|
20
|
+
def blank(*, &_block)
|
21
21
|
""
|
22
22
|
end
|
23
23
|
end
|
24
|
-
end
|
24
|
+
end
|
@@ -28,17 +28,12 @@ module Casting
|
|
28
28
|
# some_object.cast_as(Greeter, FormalGreeter)
|
29
29
|
# some_object.greet #=> 'Hello, how do you do?'
|
30
30
|
#
|
31
|
-
def super_delegate(*args, &block)
|
32
|
-
method_name = name_of_calling_method(
|
33
|
-
owner =
|
34
|
-
|
35
|
-
super_delegate_method = unbound_method_from_next_delegate(method_name, owner)
|
31
|
+
def super_delegate(mod = :none, *args, **kwargs, &block)
|
32
|
+
method_name = name_of_calling_method(caller_locations)
|
33
|
+
owner = (mod unless mod == :none) || method_delegate(method_name)
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
else
|
40
|
-
super_delegate_method.bind(self).call(*args, &block)
|
41
|
-
end
|
35
|
+
super_delegate_method = unbound_method_from_next_delegate(method_name, owner)
|
36
|
+
super_delegate_method.bind(self).call(*args, **kwargs, &block)
|
42
37
|
rescue NameError
|
43
38
|
raise NoMethodError.new("super_delegate: no delegate method `#{method_name}' for #{self.inspect} from #{owner}")
|
44
39
|
end
|
@@ -49,20 +44,31 @@ module Casting
|
|
49
44
|
|
50
45
|
def method_delegate_skipping(meth, skipped)
|
51
46
|
skipped_index = __delegates__.index(skipped)
|
52
|
-
__delegates__.find{|attendant|
|
53
|
-
attendant_methods(attendant).include?(meth)
|
54
|
-
skipped_index < __delegates__.index(attendant)
|
47
|
+
__delegates__[(skipped_index + 1)..__delegates__.length].find{|attendant|
|
48
|
+
attendant_methods(attendant).include?(meth)
|
55
49
|
}
|
56
50
|
end
|
57
|
-
|
58
|
-
def
|
51
|
+
|
52
|
+
def calling_location(call_stack)
|
59
53
|
call_stack.reject{|line|
|
60
|
-
line.to_s
|
61
|
-
}.first
|
54
|
+
line.to_s.match? Regexp.union(casting_library_matcher, gem_home_matcher, debugging_matcher)
|
55
|
+
}.first
|
56
|
+
end
|
57
|
+
|
58
|
+
def name_of_calling_method(call_stack)
|
59
|
+
calling_location(call_stack).label.to_sym
|
62
60
|
end
|
63
61
|
|
64
62
|
def casting_library_matcher
|
65
63
|
Regexp.new(Dir.pwd.to_s + '/lib')
|
66
64
|
end
|
65
|
+
|
66
|
+
def gem_home_matcher
|
67
|
+
Regexp.new(ENV['GEM_HOME'])
|
68
|
+
end
|
69
|
+
|
70
|
+
def debugging_matcher
|
71
|
+
Regexp.new('internal:trace_point')
|
72
|
+
end
|
67
73
|
end
|
68
|
-
end
|
74
|
+
end
|
data/lib/casting/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Casting
|
2
|
-
VERSION = '0.
|
3
|
-
end
|
2
|
+
VERSION = '1.0.1'
|
3
|
+
end
|
data/lib/casting.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Person
|
4
|
+
include Casting::Client
|
5
|
+
delegate_missing_methods
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
attr_reader :name
|
11
|
+
end
|
12
|
+
|
13
|
+
class PersonCollection
|
14
|
+
include Casting::Enum
|
15
|
+
|
16
|
+
def initialize(array)
|
17
|
+
@array = array
|
18
|
+
end
|
19
|
+
attr_reader :array
|
20
|
+
|
21
|
+
def each(*behaviors, &block)
|
22
|
+
enum(array, *behaviors).each(&block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Hello
|
27
|
+
def hello
|
28
|
+
"Hello, I'm #{name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Casting::Enum, '#enum' do
|
33
|
+
let(:people){
|
34
|
+
[Person.new('Jim'), Person.new('TJ'), Person.new('Sandi')]
|
35
|
+
}
|
36
|
+
it "iterates and applies behaviors to each item" do
|
37
|
+
client = PersonCollection.new(people)
|
38
|
+
assert_equal ["Hello, I'm Jim", "Hello, I'm TJ", "Hello, I'm Sandi"], client.each(Hello).map(&:hello)
|
39
|
+
end
|
40
|
+
end
|
data/test/casting_test.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
describe Casting, '.delegating' do
|
4
|
+
it 'delegates missing methods to object delegates' do
|
5
|
+
client = test_person
|
6
|
+
client.extend(Casting::Client)
|
7
|
+
client.delegate_missing_methods
|
8
|
+
|
9
|
+
attendant = test_person
|
10
|
+
attendant.extend(TestPerson::Greeter)
|
11
|
+
|
12
|
+
assert_raises(NoMethodError){
|
13
|
+
client.greet
|
14
|
+
}
|
15
|
+
Casting.delegating(client => attendant) do
|
16
|
+
assert_equal 'hello', client.greet
|
17
|
+
end
|
18
|
+
assert_raises(NoMethodError){
|
19
|
+
client.greet
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
4
23
|
it 'delegates missing methods for the objects inside the block' do
|
5
24
|
client = BlockTestPerson.new('Jim')
|
6
25
|
verbose_client = BlockTestPerson.new('Amy')
|
data/test/client_test.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestContext
|
4
|
+
using Casting::Context
|
5
|
+
extend Casting::Context
|
6
|
+
|
7
|
+
initialize :admin, :user
|
8
|
+
|
9
|
+
def approve
|
10
|
+
tell :admin, :say, 'I approve'
|
11
|
+
end
|
12
|
+
|
13
|
+
def approve_with_keyword
|
14
|
+
tell :admin, :keyword_say, what: 'I approve'
|
15
|
+
end
|
16
|
+
|
17
|
+
def user_approve
|
18
|
+
tell :user, :approve
|
19
|
+
end
|
20
|
+
|
21
|
+
module Admin
|
22
|
+
def say(what)
|
23
|
+
what
|
24
|
+
end
|
25
|
+
|
26
|
+
def keyword_say(what:)
|
27
|
+
what
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module User
|
32
|
+
def approve
|
33
|
+
'Yay!'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class MissingModuleContext
|
39
|
+
using Casting::Context
|
40
|
+
extend Casting::Context
|
41
|
+
|
42
|
+
initialize :admin, :user
|
43
|
+
|
44
|
+
def run
|
45
|
+
tell :admin, :go
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Casting::Context do
|
50
|
+
it 'applies module methods to Casting::Client objects' do
|
51
|
+
admin = casting_person
|
52
|
+
user = casting_person
|
53
|
+
|
54
|
+
context = TestContext.new admin: admin, user: user
|
55
|
+
|
56
|
+
expect(context.approve).must_equal ('I approve')
|
57
|
+
expect(context.approve_with_keyword).must_equal ('I approve')
|
58
|
+
expect(context.user_approve).must_equal ('Yay!')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'applies module methods to any object' do
|
62
|
+
admin = Object.new
|
63
|
+
user = 1
|
64
|
+
|
65
|
+
context = TestContext.new admin: admin, user: user
|
66
|
+
|
67
|
+
expect(context.approve).must_equal ('I approve')
|
68
|
+
expect(context.user_approve).must_equal ('Yay!')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'handles missing modules and raises missing method error' do
|
72
|
+
admin = TestPerson.new
|
73
|
+
user = TestPerson.new
|
74
|
+
|
75
|
+
context = MissingModuleContext.new admin: admin, user: user
|
76
|
+
|
77
|
+
err = expect{ context.run }.must_raise(NoMethodError)
|
78
|
+
expect(err.message).must_match(/unknown method 'go'/)
|
79
|
+
end
|
80
|
+
end
|
data/test/delegation_test.rb
CHANGED
@@ -3,11 +3,11 @@ require 'test_helper'
|
|
3
3
|
describe Casting::Delegation do
|
4
4
|
|
5
5
|
it 'initializes with method name and object' do
|
6
|
-
assert Casting::Delegation.
|
6
|
+
assert Casting::Delegation.prepare('some_method', Object.new)
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'raises an error when calling without an attendant object' do
|
10
|
-
delegation = Casting::Delegation.
|
10
|
+
delegation = Casting::Delegation.prepare('some_method', Object.new)
|
11
11
|
begin
|
12
12
|
delegation.call
|
13
13
|
rescue StandardError => e
|
@@ -17,7 +17,14 @@ describe Casting::Delegation do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'raises an error when setting an invalid attendant type' do
|
20
|
-
delegation = Casting::Delegation.
|
20
|
+
delegation = Casting::Delegation.prepare('some_method', TestPerson.new)
|
21
|
+
assert_raises(Casting::InvalidAttendant){
|
22
|
+
delegation.to(Unrelated.new)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises an error when setting a class as the attendant' do
|
27
|
+
delegation = Casting::Delegation.prepare('some_method', TestPerson)
|
21
28
|
assert_raises(Casting::InvalidAttendant){
|
22
29
|
delegation.to(Unrelated.new)
|
23
30
|
}
|
@@ -27,20 +34,36 @@ describe Casting::Delegation do
|
|
27
34
|
attendant = test_person
|
28
35
|
client = SubTestPerson.new
|
29
36
|
|
30
|
-
delegation = Casting::Delegation.
|
37
|
+
delegation = Casting::Delegation.prepare('name', client)
|
31
38
|
assert delegation.to(attendant)
|
32
39
|
end
|
33
40
|
|
34
41
|
it 'delegates when given a module' do
|
35
42
|
client = test_person
|
36
|
-
delegation = Casting::Delegation.
|
43
|
+
delegation = Casting::Delegation.prepare('greet', client).to(TestPerson::Greeter)
|
37
44
|
assert_equal 'hello', delegation.call
|
38
45
|
end
|
39
46
|
|
40
47
|
it 'does not delegate when given a class' do
|
41
48
|
client = test_person
|
49
|
+
err = expect{
|
50
|
+
Casting::Delegation.prepare('class_defined', client).to(Unrelated)
|
51
|
+
}.must_raise(TypeError)
|
52
|
+
expect(err.message).must_match(/ argument must be a module or an object with/)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'finds the module defining a method and uses it to delegate' do
|
56
|
+
client = test_person
|
57
|
+
attendant = Unrelated.new
|
58
|
+
delegation = Casting::Delegation.prepare('unrelated', client).to(attendant)
|
59
|
+
assert_equal attendant.unrelated, delegation.call
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not delegate to methods defined in classes' do
|
63
|
+
client = test_person
|
64
|
+
attendant = Unrelated.new
|
42
65
|
assert_raises(TypeError){
|
43
|
-
Casting::Delegation.
|
66
|
+
Casting::Delegation.prepare('class_defined', client).to(attendant)
|
44
67
|
}
|
45
68
|
end
|
46
69
|
|
@@ -48,17 +71,128 @@ describe Casting::Delegation do
|
|
48
71
|
client = test_person
|
49
72
|
attendant = TestPerson::Verbose
|
50
73
|
|
51
|
-
delegation = Casting::Delegation.
|
74
|
+
delegation = Casting::Delegation.prepare('verbose', client).to(attendant)
|
52
75
|
|
53
76
|
assert_equal 'hello,goodbye', delegation.with('hello', 'goodbye').call
|
54
77
|
end
|
55
78
|
|
79
|
+
it 'assigns keyword arguments to the delegated method using with' do
|
80
|
+
client = test_person
|
81
|
+
attendant = TestPerson::Verbose
|
82
|
+
|
83
|
+
delegation = Casting::Delegation.prepare('verbose_keywords', client).to(attendant)
|
84
|
+
|
85
|
+
assert_equal 'hello,goodbye', delegation.with(key: 'hello', word: 'goodbye').call
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'assigns regular and keyword arguments to the delegated method using with' do
|
89
|
+
client = test_person
|
90
|
+
attendant = TestPerson::Verbose
|
91
|
+
|
92
|
+
delegation = Casting::Delegation.prepare('verbose_multi_args', client).to(attendant)
|
93
|
+
|
94
|
+
assert_equal('hello,goodbye,keys,words,block!', delegation.with('hello', 'goodbye', key: 'keys', word: 'words') do
|
95
|
+
"block!"
|
96
|
+
end.call)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'handles flexible arguments to the delegated method using with' do
|
100
|
+
client = test_person
|
101
|
+
attendant = TestPerson::Verbose
|
102
|
+
|
103
|
+
delegation = Casting::Delegation.prepare('verbose_flex', client).to(attendant)
|
104
|
+
|
105
|
+
assert_equal('hello,key:keys,word:words,block!', delegation.with('hello', key: 'keys', word: 'words') do
|
106
|
+
"block!"
|
107
|
+
end.call)
|
108
|
+
end
|
109
|
+
|
56
110
|
it 'prefers `call` arguments over `with`' do
|
57
111
|
client = test_person
|
58
112
|
attendant = TestPerson::Verbose
|
59
113
|
|
60
|
-
delegation = Casting::Delegation.
|
114
|
+
delegation = Casting::Delegation.prepare('verbose', client).to(attendant)
|
61
115
|
|
62
116
|
assert_equal 'call,args', delegation.with('hello', 'goodbye').call('call','args')
|
63
117
|
end
|
64
|
-
|
118
|
+
|
119
|
+
it 'prefers "call" keyword arguments over "with"' do
|
120
|
+
client = test_person
|
121
|
+
attendant = TestPerson::Verbose
|
122
|
+
|
123
|
+
delegation = Casting::Delegation.prepare('verbose_keywords', client).to(attendant)
|
124
|
+
|
125
|
+
assert_equal 'call,args', delegation.with(key: 'hello', word: 'goodbye').call(key: 'call', word: 'args')
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'prefers "call" regular and keyword arguments over "with"' do
|
129
|
+
client = test_person
|
130
|
+
attendant = TestPerson::Verbose
|
131
|
+
|
132
|
+
delegation = Casting::Delegation.prepare('verbose_multi_args', client).to(attendant)
|
133
|
+
|
134
|
+
assert_equal 'hello,goodbye,call,args', delegation.with('this', 'that', key: 'something', word: 'else').call('hello', 'goodbye', key: 'call', word: 'args')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'prefers "call" block arguments over "with"' do
|
138
|
+
client = test_person
|
139
|
+
attendant = TestPerson::Verbose
|
140
|
+
|
141
|
+
delegation = Casting::Delegation.prepare('verbose_multi_args', client).to(attendant)
|
142
|
+
|
143
|
+
prepared = delegation.with('this', 'that', key: 'something', word: 'else') { "prepared block!" }
|
144
|
+
|
145
|
+
assert_equal('this,that,something,else,call block!', prepared.call{ "call block!" })
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'prefers "call" keyword arguments and block over "with"' do
|
149
|
+
client = test_person
|
150
|
+
attendant = TestPerson::Verbose
|
151
|
+
|
152
|
+
delegation = Casting::Delegation.prepare('verbose_flex', client).to(attendant)
|
153
|
+
|
154
|
+
prepared = delegation.with(key: 'something', word: 'else') { "prepared block!" }
|
155
|
+
|
156
|
+
assert_equal('key:call_key,word:call_word,call block!', prepared.call(key: 'call_key', word: 'call_word'){ "call block!" })
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'prefers "call" and block over "with"' do
|
160
|
+
client = test_person
|
161
|
+
attendant = TestPerson::Verbose
|
162
|
+
|
163
|
+
delegation = Casting::Delegation.prepare('verbose_flex', client).to(attendant)
|
164
|
+
|
165
|
+
prepared = delegation.with { "prepared block!" }
|
166
|
+
|
167
|
+
assert_equal('call block!', prepared.call { "call block!" })
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'calls a method defined on another object of the same type' do
|
171
|
+
client = test_person
|
172
|
+
attendant = test_person
|
173
|
+
attendant.extend(TestPerson::Greeter)
|
174
|
+
delegation = Casting::Delegation.prepare('greet', client).to(attendant)
|
175
|
+
assert_equal 'hello', delegation.call
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'passes arguments to a delegated method' do
|
179
|
+
client = test_person
|
180
|
+
attendant = test_person
|
181
|
+
attendant.extend(TestPerson::Verbose)
|
182
|
+
delegation = Casting::Delegation.prepare('verbose', client).to(attendant).with('arg1','arg2')
|
183
|
+
assert_equal 'arg1,arg2', delegation.call
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'delegates when given a module' do
|
187
|
+
client = test_person
|
188
|
+
delegation = Casting::Delegation.prepare('greet', client).to(TestPerson::Greeter)
|
189
|
+
assert_equal 'hello', delegation.call
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not delegate when given a class' do
|
193
|
+
client = test_person
|
194
|
+
assert_raises(TypeError){
|
195
|
+
Casting::Delegation.prepare('class_defined', client).to(Unrelated)
|
196
|
+
}
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Casting::Client do
|
4
|
+
it 'will not attempt to alter a frozen client' do
|
5
|
+
client = TestPerson.new
|
6
|
+
client.extend(Casting::Client)
|
7
|
+
client.delegate_missing_methods
|
8
|
+
|
9
|
+
client.freeze
|
10
|
+
|
11
|
+
err = expect{ client.greet }.must_raise(NoMethodError)
|
12
|
+
expect(err.message).must_match(/undefined method \`greet'/)
|
13
|
+
end
|
14
|
+
end
|
data/test/null_module_test.rb
CHANGED
@@ -21,6 +21,17 @@ describe Casting::Blank do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
describe "making null objects" do
|
24
|
+
it "answers to missing methods" do
|
25
|
+
client = TestPerson.new
|
26
|
+
client.extend(Casting::Client)
|
27
|
+
client.delegate_missing_methods
|
28
|
+
attendant = Casting::Null
|
29
|
+
|
30
|
+
assert_respond_to client.cast_as(attendant), 'xyz'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "making blank objects" do
|
24
35
|
it "answers to missing methods" do
|
25
36
|
client = TestPerson.new
|
26
37
|
client.extend(Casting::Client)
|
data/test/super_test.rb
CHANGED
@@ -4,19 +4,40 @@ module AnyWay
|
|
4
4
|
def which_way
|
5
5
|
"any way"
|
6
6
|
end
|
7
|
+
def way_with_args(one, two, &block)
|
8
|
+
[one, two, block&.call].compact.inspect
|
9
|
+
end
|
10
|
+
def way_with_keyword_args(one:, two:, &block)
|
11
|
+
[one, two, block&.call].compact.inspect
|
12
|
+
end
|
7
13
|
end
|
8
14
|
|
9
15
|
module ThisWay
|
10
16
|
include Casting::SuperDelegate
|
11
17
|
def which_way
|
12
|
-
"this way or #{super_delegate
|
18
|
+
"this way or #{super_delegate}"
|
19
|
+
end
|
20
|
+
def way_with_args(one, two, &block)
|
21
|
+
[one, two, block&.call].compact.inspect
|
22
|
+
end
|
23
|
+
def way_with_keyword_args(one:, two:, &block)
|
24
|
+
[one, two, block&.call].compact.inspect
|
25
|
+
end
|
26
|
+
def no_super
|
27
|
+
super_delegate
|
13
28
|
end
|
14
29
|
end
|
15
30
|
|
16
31
|
module ThatWay
|
17
32
|
include Casting::SuperDelegate
|
18
33
|
def which_way
|
19
|
-
"#{ super_delegate } and that way!"
|
34
|
+
"#{ super_delegate(ThatWay) } and that way!"
|
35
|
+
end
|
36
|
+
def way_with_args(one, two, &block)
|
37
|
+
super_delegate(one, two, block&.call).compact
|
38
|
+
end
|
39
|
+
def way_with_keyword_args(one:, two:, &block)
|
40
|
+
[one, two, block&.call].compact.inspect
|
20
41
|
end
|
21
42
|
end
|
22
43
|
|
@@ -24,8 +45,36 @@ describe Casting, 'modules using delegate_super' do
|
|
24
45
|
it 'call the method from the next delegate with the same arguments' do
|
25
46
|
client = TestPerson.new.extend(Casting::Client)
|
26
47
|
client.delegate_missing_methods
|
27
|
-
client.cast_as(AnyWay,
|
48
|
+
client.cast_as(AnyWay, ThatWay, ThisWay)
|
28
49
|
|
29
50
|
assert_equal 'this way or any way and that way!', client.which_way
|
30
51
|
end
|
31
|
-
|
52
|
+
|
53
|
+
it 'passes arguments' do
|
54
|
+
client = TestPerson.new.extend(Casting::Client)
|
55
|
+
client.delegate_missing_methods
|
56
|
+
client.cast_as(ThatWay, ThisWay)
|
57
|
+
|
58
|
+
assert_equal %{["first", "second", "block"]}, client.way_with_args('first', 'second'){ 'block' }
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'passes keyword arguments' do
|
62
|
+
client = TestPerson.new.extend(Casting::Client)
|
63
|
+
client.delegate_missing_methods
|
64
|
+
client.cast_as(ThatWay, ThisWay)
|
65
|
+
|
66
|
+
assert_equal %{["first", "second", "block"]}, client.way_with_keyword_args(one: 'first', two: 'second'){ 'block' }
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'raises an error when method is not defined' do
|
70
|
+
client = TestPerson.new.extend(Casting::Client)
|
71
|
+
client.delegate_missing_methods
|
72
|
+
client.cast_as(ThisWay)
|
73
|
+
|
74
|
+
err = expect{
|
75
|
+
client.no_super
|
76
|
+
}.must_raise(NoMethodError)
|
77
|
+
|
78
|
+
expect(err.message).must_match(/super_delegate: no delegate method \`no_super' for \#<TestPerson:\dx[a-z0-9]*> from ThisWay/)
|
79
|
+
end
|
80
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "simplecov"
|
2
2
|
SimpleCov.start do
|
3
3
|
add_filter 'test'
|
4
4
|
end
|
5
|
-
|
6
|
-
require 'coveralls'
|
7
|
-
if ENV['COVERALLS']
|
8
|
-
Coveralls.wear!
|
9
|
-
end
|
10
|
-
|
11
5
|
require 'minitest/autorun'
|
12
6
|
require 'casting'
|
13
7
|
|
@@ -38,9 +32,25 @@ class TestPerson
|
|
38
32
|
def verbose(arg1, arg2)
|
39
33
|
[arg1, arg2].join(',')
|
40
34
|
end
|
35
|
+
|
36
|
+
def verbose_keywords(key:, word:)
|
37
|
+
[key, word].join(',')
|
38
|
+
end
|
39
|
+
|
40
|
+
def verbose_multi_args(arg1, arg2, key:, word:, &block)
|
41
|
+
[arg1, arg2, key, word, block&.call].compact.join(',')
|
42
|
+
end
|
43
|
+
|
44
|
+
def verbose_flex(*args, **kwargs, &block)
|
45
|
+
[args, kwargs.map{|k,v| "#{k}:#{v}"}, block&.call].flatten.compact.join(',')
|
46
|
+
end
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
50
|
+
class TestGreeter
|
51
|
+
include TestPerson::Greeter
|
52
|
+
end
|
53
|
+
|
44
54
|
class SubTestPerson < TestPerson
|
45
55
|
def sub_method
|
46
56
|
'sub'
|
@@ -81,3 +91,7 @@ end
|
|
81
91
|
def test_person
|
82
92
|
TestPerson.new
|
83
93
|
end
|
94
|
+
|
95
|
+
def casting_person
|
96
|
+
test_person.extend(Casting::Client)
|
97
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: casting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Gay
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |-
|
14
14
|
Casting assists in method delegation which preserves the binding of 'self' to the object receiving a message.
|
@@ -20,65 +20,67 @@ executables: []
|
|
20
20
|
extensions: []
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
23
26
|
- lib/casting.rb
|
24
27
|
- lib/casting/client.rb
|
28
|
+
- lib/casting/context.rb
|
25
29
|
- lib/casting/delegation.rb
|
30
|
+
- lib/casting/enum.rb
|
26
31
|
- lib/casting/method_consolidator.rb
|
27
32
|
- lib/casting/missing_method_client.rb
|
28
33
|
- lib/casting/missing_method_client_class.rb
|
29
34
|
- lib/casting/null.rb
|
30
|
-
- lib/casting/prepared_delegation.rb
|
31
35
|
- lib/casting/super_delegate.rb
|
32
36
|
- lib/casting/version.rb
|
33
|
-
-
|
34
|
-
- Rakefile
|
35
|
-
- README.md
|
36
|
-
- test/test_helper.rb
|
37
|
-
- test/casting_19_test.rb
|
38
|
-
- test/casting_20_test.rb
|
37
|
+
- test/casting_enum_test.rb
|
39
38
|
- test/casting_test.rb
|
40
39
|
- test/class_refinement_test.rb
|
41
40
|
- test/client_test.rb
|
41
|
+
- test/context_test.rb
|
42
42
|
- test/delegation_test.rb
|
43
|
+
- test/frozen_client_test.rb
|
43
44
|
- test/method_consolidator_test.rb
|
44
45
|
- test/missing_method_client_test.rb
|
45
|
-
- test/null_module_test.rb
|
46
46
|
- test/module_cleanup_test.rb
|
47
|
+
- test/null_module_test.rb
|
47
48
|
- test/super_test.rb
|
49
|
+
- test/test_helper.rb
|
48
50
|
homepage: http://github.com/saturnflyer/casting
|
49
51
|
licenses:
|
50
52
|
- MIT
|
51
53
|
metadata: {}
|
52
|
-
post_install_message:
|
54
|
+
post_install_message:
|
53
55
|
rdoc_options: []
|
54
56
|
require_paths:
|
55
57
|
- lib
|
56
58
|
required_ruby_version: !ruby/object:Gem::Requirement
|
57
59
|
requirements:
|
58
|
-
- -
|
60
|
+
- - ">="
|
59
61
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
62
|
+
version: '2.7'
|
61
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
64
|
requirements:
|
63
|
-
- -
|
65
|
+
- - ">="
|
64
66
|
- !ruby/object:Gem::Version
|
65
67
|
version: '0'
|
66
68
|
requirements: []
|
67
|
-
|
68
|
-
|
69
|
-
signing_key:
|
69
|
+
rubygems_version: 3.2.27
|
70
|
+
signing_key:
|
70
71
|
specification_version: 4
|
71
72
|
summary: Proper method delegation.
|
72
73
|
test_files:
|
73
74
|
- test/test_helper.rb
|
74
|
-
- test/
|
75
|
-
- test/casting_20_test.rb
|
75
|
+
- test/casting_enum_test.rb
|
76
76
|
- test/casting_test.rb
|
77
77
|
- test/class_refinement_test.rb
|
78
78
|
- test/client_test.rb
|
79
|
+
- test/context_test.rb
|
79
80
|
- test/delegation_test.rb
|
81
|
+
- test/frozen_client_test.rb
|
80
82
|
- test/method_consolidator_test.rb
|
81
83
|
- test/missing_method_client_test.rb
|
82
|
-
- test/null_module_test.rb
|
83
84
|
- test/module_cleanup_test.rb
|
85
|
+
- test/null_module_test.rb
|
84
86
|
- test/super_test.rb
|
@@ -1,76 +0,0 @@
|
|
1
|
-
module Casting
|
2
|
-
|
3
|
-
class MissingAttendant < StandardError
|
4
|
-
def message
|
5
|
-
"You must set your attendant object using `to'."
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
class InvalidAttendant < StandardError; end
|
10
|
-
|
11
|
-
class PreparedDelegation
|
12
|
-
|
13
|
-
attr_reader :client
|
14
|
-
attr_reader :delegated_method_name, :attendant, :arguments, :block
|
15
|
-
private :delegated_method_name, :attendant, :arguments, :block
|
16
|
-
|
17
|
-
def initialize(settings)
|
18
|
-
@delegated_method_name = settings[:delegated_method_name]
|
19
|
-
@client = settings[:client]
|
20
|
-
@attendant = settings[:attendant]
|
21
|
-
@arguments = settings[:arguments]
|
22
|
-
end
|
23
|
-
|
24
|
-
def to(object_or_module)
|
25
|
-
@attendant = object_or_module
|
26
|
-
begin
|
27
|
-
check_valid_type
|
28
|
-
rescue TypeError
|
29
|
-
@attendant = method_module || raise
|
30
|
-
end
|
31
|
-
self
|
32
|
-
end
|
33
|
-
|
34
|
-
def with(*args, &block)
|
35
|
-
@arguments = args
|
36
|
-
@block = block
|
37
|
-
self
|
38
|
-
end
|
39
|
-
|
40
|
-
def call(*args)
|
41
|
-
@arguments = args unless args.empty?
|
42
|
-
raise MissingAttendant.new unless attendant
|
43
|
-
|
44
|
-
if arguments
|
45
|
-
delegated_method.bind(client).call(*arguments, &block)
|
46
|
-
else
|
47
|
-
delegated_method.bind(client).call
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def check_valid_type
|
54
|
-
begin
|
55
|
-
!client.nil? && delegated_method.bind(client)
|
56
|
-
rescue TypeError
|
57
|
-
raise TypeError.new("`to' argument must be a module or an object with #{delegated_method_name} defined in a module")
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def method_module
|
62
|
-
delegated_method.owner unless delegated_method.owner.is_a?(Class)
|
63
|
-
end
|
64
|
-
|
65
|
-
def delegated_method
|
66
|
-
if Module === attendant
|
67
|
-
attendant.instance_method(delegated_method_name)
|
68
|
-
else
|
69
|
-
attendant.method(delegated_method_name).owner.instance_method(delegated_method_name)
|
70
|
-
end
|
71
|
-
rescue NameError => e
|
72
|
-
raise InvalidAttendant.new(e.message)
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
data/test/casting_19_test.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe Casting, '.delegating' do
|
4
|
-
it 'delegates missing methods to object delegates' do
|
5
|
-
client = test_person
|
6
|
-
client.extend(Casting::Client)
|
7
|
-
client.delegate_missing_methods
|
8
|
-
|
9
|
-
attendant = test_person
|
10
|
-
attendant.extend(TestPerson::Greeter)
|
11
|
-
|
12
|
-
assert_raises(NoMethodError){
|
13
|
-
client.greet
|
14
|
-
}
|
15
|
-
Casting.delegating(client => attendant) do
|
16
|
-
assert_equal 'hello', client.greet
|
17
|
-
end
|
18
|
-
assert_raises(NoMethodError){
|
19
|
-
client.greet
|
20
|
-
}
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe Casting::Delegation do
|
25
|
-
|
26
|
-
it 'calls a method defined on another object of the same type' do
|
27
|
-
client = test_person
|
28
|
-
attendant = test_person
|
29
|
-
attendant.extend(TestPerson::Greeter)
|
30
|
-
delegation = Casting::Delegation.new('greet', client).to(attendant)
|
31
|
-
assert_equal 'hello', delegation.call
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'passes arguments to a delegated method' do
|
35
|
-
client = test_person
|
36
|
-
attendant = test_person
|
37
|
-
attendant.extend(TestPerson::Verbose)
|
38
|
-
delegation = Casting::Delegation.new('verbose', client).to(attendant).with('arg1','arg2')
|
39
|
-
assert_equal 'arg1,arg2', delegation.call
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'delegates when given a module' do
|
43
|
-
client = test_person
|
44
|
-
delegation = Casting::Delegation.new('greet', client).to(TestPerson::Greeter)
|
45
|
-
assert_equal 'hello', delegation.call
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'does not delegate when given a class' do
|
49
|
-
client = test_person
|
50
|
-
assert_raises(TypeError){
|
51
|
-
Casting::Delegation.new('class_defined', client).to(Unrelated)
|
52
|
-
}
|
53
|
-
end
|
54
|
-
end
|
data/test/casting_20_test.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe Casting::Delegation do
|
4
|
-
|
5
|
-
it 'finds the module defining a method and uses it to delegate' do
|
6
|
-
client = test_person
|
7
|
-
attendant = Unrelated.new
|
8
|
-
delegation = Casting::Delegation.new('unrelated', client).to(attendant)
|
9
|
-
assert_equal attendant.unrelated, delegation.call
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'does not delegate to methods defined in classes' do
|
13
|
-
client = test_person
|
14
|
-
attendant = Unrelated.new
|
15
|
-
assert_raises(TypeError){
|
16
|
-
Casting::Delegation.new('class_defined', client).to(attendant)
|
17
|
-
}
|
18
|
-
end
|
19
|
-
end
|