casting 0.7.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/saturnflyer/casting.png?branch=master)](https://travis-ci.org/saturnflyer/casting)
|
4
3
|
[![Code Climate](https://codeclimate.com/github/saturnflyer/casting.png)](https://codeclimate.com/github/saturnflyer/casting)
|
5
|
-
[![Coverage
|
4
|
+
[![Test Coverage](https://codeclimate.com/github/saturnflyer/casting/badges/coverage.svg)](https://codeclimate.com/github/saturnflyer/casting/coverage)
|
6
5
|
[![Gem Version](https://badge.fury.io/rb/casting.png)](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
|