casting 1.0.1 → 1.0.3
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 +4 -4
- data/README.md +45 -5
- data/Rakefile +11 -4
- data/lib/casting/client.rb +12 -13
- data/lib/casting/context.rb +11 -16
- data/lib/casting/delegation.rb +33 -35
- data/lib/casting/enum.rb +3 -1
- data/lib/casting/method_consolidator.rb +5 -5
- data/lib/casting/missing_method_client.rb +18 -19
- data/lib/casting/missing_method_client_class.rb +3 -4
- data/lib/casting/null.rb +8 -2
- data/lib/casting/super_delegate.rb +38 -14
- data/lib/casting/version.rb +1 -1
- data/lib/casting.rb +6 -8
- metadata +4 -33
- data/test/casting_enum_test.rb +0 -40
- data/test/casting_test.rb +0 -89
- data/test/class_refinement_test.rb +0 -119
- data/test/client_test.rb +0 -81
- data/test/context_test.rb +0 -80
- data/test/delegation_test.rb +0 -198
- data/test/frozen_client_test.rb +0 -14
- data/test/method_consolidator_test.rb +0 -81
- data/test/missing_method_client_test.rb +0 -203
- data/test/module_cleanup_test.rb +0 -43
- data/test/null_module_test.rb +0 -43
- data/test/super_test.rb +0 -80
- data/test/test_helper.rb +0 -97
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e9ce2a6479f584071216d9aa62c7f076525325295cbd4df491dd6cbf4ffad39
|
|
4
|
+
data.tar.gz: 3c1475f0c68d62ac2ba37c542b5c7d0a8c6fddad91a88a7097c03dcdaa02f5d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4942f847d1219c074e2794e8079a22c37bccfb0f011502944835c2b4b1aa1413f957fba526545fc66ea3d53f35e0cad5de206ec2560be51e2b4123624161f42f
|
|
7
|
+
data.tar.gz: c8b62a94bade49af44f49c49fdf1bca3075dfc68a7b65d57f31f4e6cd201fa32915ad1e5f7c6491806119000e3d350233331fcf5bfca94e8567c1c46b319f388
|
data/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Casting
|
|
2
2
|
|
|
3
|
-
[](https://codeclimate.com/github/saturnflyer/casting)
|
|
4
|
-
[](https://codeclimate.com/github/saturnflyer/casting/coverage)
|
|
5
3
|
[](http://badge.fury.io/rb/casting)
|
|
6
4
|
|
|
7
5
|
## Add behavior to your objects without using extend
|
|
6
|
+
|
|
8
7
|
Do it for the life of the object or only for the life of a block of code.
|
|
9
8
|
|
|
10
9
|
Casting gives you real delegation that flattens your object structure compared to libraries
|
|
@@ -53,7 +52,7 @@ end
|
|
|
53
52
|
actor = Actor.new
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
Your objects will have a few additional methods: `delegation`, `cast`, and if you do not
|
|
55
|
+
Your objects will have a few additional methods: `delegation`, `cast`, and if you do not _already_ have it defined (from another library, for example): `delegate`. The `delegate` method is aliased to `cast`.
|
|
57
56
|
|
|
58
57
|
Then you may delegate a method to an attendant object:
|
|
59
58
|
|
|
@@ -327,11 +326,11 @@ module Special
|
|
|
327
326
|
def self.cast_object(obj)
|
|
328
327
|
obj.instance_variable_set(:@special_value, 'this is special!')
|
|
329
328
|
end
|
|
330
|
-
|
|
329
|
+
|
|
331
330
|
def self.uncast_object(obj)
|
|
332
331
|
obj.remove_instance_variable(:@special_value)
|
|
333
332
|
end
|
|
334
|
-
|
|
333
|
+
|
|
335
334
|
def special_behavior
|
|
336
335
|
"#{self.name} thinks... #{@special_value}"
|
|
337
336
|
end
|
|
@@ -345,6 +344,47 @@ object.uncast
|
|
|
345
344
|
|
|
346
345
|
You'll be able to leave your objects as if they were never touched by the module where you defined your behavior.
|
|
347
346
|
|
|
347
|
+
## It doesn't work!
|
|
348
|
+
|
|
349
|
+
You might be trying to override existing methods. Casting can help you apply behavior to an object using `delegate_missing_methods` but that depends on the methods being missing. In other words, if you have an `as_json` method that you want to change with a module, you won't be able to just `cast_as(MyJsonModule)` and have the `as_json` method from it be picked up because that will never hit `method_missing`.
|
|
350
|
+
|
|
351
|
+
If you want to override an existing method, you must do so explicitly.
|
|
352
|
+
|
|
353
|
+
This will _not_ work:
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
module MyJsonModule
|
|
357
|
+
def as_json
|
|
358
|
+
super.merge({ extra: 'details' })
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
some_object.cast_as(MyJsonModule)
|
|
362
|
+
some_object.as_json
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Instead, you'll need to explicitly override existing methods:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
some_object.cast(:as_json, MyJsonModule)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## How can I speed it up?
|
|
372
|
+
|
|
373
|
+
Are you looping over lots of objects and want see better performance?
|
|
374
|
+
|
|
375
|
+
If you want to make things a bit faster, you can prepare the method delegation ahead of time and change the client object.
|
|
376
|
+
|
|
377
|
+
```ruby
|
|
378
|
+
prepared_delegation = some_object.delegation(:some_delegated_method).to(MySpecialModule)
|
|
379
|
+
# Some looping code
|
|
380
|
+
big_list_of_objects.each do |object|
|
|
381
|
+
prepared_delegation.client = object
|
|
382
|
+
prepared_delegation.call
|
|
383
|
+
end
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Preparing the delegated method like this will probably speed things up for you but be sure to verify for yourself.
|
|
387
|
+
|
|
348
388
|
## Installation
|
|
349
389
|
|
|
350
390
|
If you are using Bundler, add this line to your application's Gemfile:
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env rake
|
|
2
2
|
require "bundler/gem_tasks"
|
|
3
|
-
require
|
|
3
|
+
require "rake/testtask"
|
|
4
4
|
|
|
5
5
|
Rake::TestTask.new do |t|
|
|
6
|
-
t.libs <<
|
|
7
|
-
t.test_files = FileList[
|
|
6
|
+
t.libs << "test"
|
|
7
|
+
t.test_files = FileList["test/*_test.rb"]
|
|
8
8
|
t.ruby_opts = ["-w"]
|
|
9
9
|
t.verbose = true
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
require "reissue/gem"
|
|
13
|
+
|
|
14
|
+
Reissue::Task.create do |task|
|
|
15
|
+
task.version_file = "lib/casting/version.rb"
|
|
16
|
+
task.fragment = :git
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
task default: :test
|
data/lib/casting/client.rb
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "casting/delegation"
|
|
2
|
+
require "casting/missing_method_client"
|
|
3
|
+
require "casting/missing_method_client_class"
|
|
4
4
|
|
|
5
5
|
module Casting
|
|
6
6
|
module Client
|
|
7
|
-
|
|
8
7
|
def self.included(base)
|
|
9
8
|
def base.delegate_missing_methods(*which)
|
|
10
9
|
Casting::Client.set_delegation_strategy(self, *which.reverse)
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
unless base.method_defined?(
|
|
12
|
+
unless base.method_defined?(:delegate)
|
|
14
13
|
add_delegate_method_to(base)
|
|
15
14
|
end
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def self.extended(base)
|
|
19
|
-
unless base.respond_to?(
|
|
18
|
+
unless base.respond_to?(:delegate)
|
|
20
19
|
add_delegate_method_to(base.singleton_class)
|
|
21
20
|
end
|
|
22
21
|
end
|
|
@@ -31,28 +30,28 @@ module Casting
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def delegate_missing_methods(*which)
|
|
34
|
-
Casting::Client.set_delegation_strategy(
|
|
33
|
+
Casting::Client.set_delegation_strategy(singleton_class, *which.reverse)
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
private
|
|
38
37
|
|
|
39
38
|
def validate_attendant(attendant)
|
|
40
39
|
if attendant == self
|
|
41
|
-
raise Casting::InvalidAttendant.new(
|
|
40
|
+
raise Casting::InvalidAttendant.new("client can not delegate to itself")
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
def self.set_delegation_strategy(base, *which)
|
|
46
45
|
which = [:instance] if which.empty?
|
|
47
|
-
which.map!{|selection|
|
|
48
|
-
selection == :instance && selection =
|
|
49
|
-
selection == :class && selection =
|
|
46
|
+
which.map! { |selection|
|
|
47
|
+
selection == :instance && selection = method(:set_method_missing_client)
|
|
48
|
+
selection == :class && selection = method(:set_method_missing_client_class)
|
|
50
49
|
selection
|
|
51
|
-
}.map{|meth| meth.call(base) }
|
|
50
|
+
}.map { |meth| meth.call(base) }
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
def self.add_delegate_method_to(base)
|
|
55
|
-
base.class_eval{ alias_method :delegate, :cast }
|
|
54
|
+
base.class_eval { alias_method :delegate, :cast }
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
def self.set_method_missing_client(base)
|
data/lib/casting/context.rb
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
#
|
|
30
30
|
module Casting
|
|
31
31
|
module Context
|
|
32
|
-
|
|
33
32
|
def self.extended(base)
|
|
34
33
|
base.send(:include, InstanceMethods)
|
|
35
34
|
end
|
|
@@ -38,26 +37,21 @@ module Casting
|
|
|
38
37
|
attr_reader(*setup_args)
|
|
39
38
|
private(*setup_args)
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
define_method(:__custom_initialize, &block)
|
|
43
|
-
else
|
|
44
|
-
define_method(:__custom_initialize) do; end
|
|
45
|
-
end
|
|
40
|
+
define_method(:__custom_initialize, &(block || proc {}))
|
|
46
41
|
|
|
47
42
|
mod = Module.new
|
|
48
|
-
|
|
49
|
-
def initialize(#{setup_args.map{|name| "#{name}:" }.join(
|
|
43
|
+
mod.class_eval <<~INIT, __FILE__, __LINE__ + 1
|
|
44
|
+
def initialize(#{setup_args.map { |name| "#{name}:" }.join(",")})
|
|
50
45
|
@assignments = []
|
|
51
46
|
#{setup_args.map do |name|
|
|
52
|
-
["assign(",name,", '",name,"')"].join
|
|
47
|
+
["assign(", name, ", '", name, "')"].join
|
|
53
48
|
end.join("\n")}
|
|
54
49
|
__custom_initialize
|
|
55
50
|
Thread.current[:context] = self
|
|
56
51
|
end
|
|
57
52
|
attr_reader :assignments
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const_set('Initializer', mod)
|
|
53
|
+
INIT
|
|
54
|
+
const_set(:Initializer, mod)
|
|
61
55
|
include mod
|
|
62
56
|
end
|
|
63
57
|
|
|
@@ -73,7 +67,7 @@ module Casting
|
|
|
73
67
|
# Keep track of objects and their behaviors
|
|
74
68
|
def assign(object, role_name)
|
|
75
69
|
instance_variable_set("@#{role_name}", object)
|
|
76
|
-
|
|
70
|
+
assignments << [object, role_for(role_name)]
|
|
77
71
|
end
|
|
78
72
|
|
|
79
73
|
def contains?(obj)
|
|
@@ -91,12 +85,12 @@ module Casting
|
|
|
91
85
|
|
|
92
86
|
# Find the first assigned role which implements a response for the given method name
|
|
93
87
|
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}")
|
|
88
|
+
assigned_roles(object).find { |role| role.method_defined?(method_name) } || raise(NoMethodError, "unknown method '#{method_name}' expected for #{object}")
|
|
95
89
|
end
|
|
96
90
|
|
|
97
91
|
# Get the roles for the given object
|
|
98
92
|
def assigned_roles(object)
|
|
99
|
-
assignments.select{|pair|
|
|
93
|
+
assignments.select { |pair|
|
|
100
94
|
pair.first == object
|
|
101
95
|
}.map(&:last)
|
|
102
96
|
end
|
|
@@ -104,7 +98,8 @@ module Casting
|
|
|
104
98
|
# Get the behavior module for the named role.
|
|
105
99
|
# This role constant for special_person is SpecialPerson.
|
|
106
100
|
def role_for(name)
|
|
107
|
-
|
|
101
|
+
# Convert snake_case to CamelCase
|
|
102
|
+
role_name = name.to_s.split('_').map(&:capitalize).join
|
|
108
103
|
self.class.const_get(role_name)
|
|
109
104
|
rescue NameError
|
|
110
105
|
Module.new
|
data/lib/casting/delegation.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
module Casting
|
|
2
|
-
|
|
3
2
|
class MissingAttendant < StandardError
|
|
4
3
|
def message
|
|
5
4
|
"You must set your attendant object using `to'."
|
|
@@ -9,7 +8,6 @@ module Casting
|
|
|
9
8
|
class InvalidAttendant < StandardError; end
|
|
10
9
|
|
|
11
10
|
class Delegation
|
|
12
|
-
|
|
13
11
|
def self.prepare(delegated_method_name, client, &block)
|
|
14
12
|
new(delegated_method_name: delegated_method_name, client: client, &block)
|
|
15
13
|
end
|
|
@@ -46,41 +44,42 @@ module Casting
|
|
|
46
44
|
def call(*args, **kwargs, &block)
|
|
47
45
|
raise MissingAttendant.new unless attendant
|
|
48
46
|
|
|
49
|
-
call_args =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
47
|
+
call_args = positional_arguments(args)
|
|
48
|
+
call_kwargs = keyword_arguments(kwargs)
|
|
49
|
+
call_block = block_argument(&block)
|
|
50
|
+
|
|
51
|
+
case
|
|
52
|
+
when call_args && call_kwargs
|
|
53
|
+
bound_method.call(*call_args, **call_kwargs, &call_block)
|
|
54
|
+
when call_args
|
|
55
|
+
bound_method.call(*call_args, &call_block)
|
|
56
|
+
when call_kwargs
|
|
57
|
+
bound_method.call(**call_kwargs, &call_block)
|
|
67
58
|
else
|
|
68
|
-
|
|
69
|
-
bound_method.call(**call_kwargs, &call_block)
|
|
70
|
-
else
|
|
71
|
-
bound_method.call(&call_block)
|
|
72
|
-
end
|
|
59
|
+
bound_method.call(&call_block)
|
|
73
60
|
end
|
|
74
61
|
end
|
|
75
62
|
|
|
76
63
|
private
|
|
77
64
|
|
|
65
|
+
def block_argument(&block)
|
|
66
|
+
block || @block
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def positional_arguments(options)
|
|
70
|
+
return options unless options.empty?
|
|
71
|
+
@arguments
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def keyword_arguments(options)
|
|
75
|
+
return options unless options.empty?
|
|
76
|
+
@keyword_arguments
|
|
77
|
+
end
|
|
78
|
+
|
|
78
79
|
def bound_method
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
raise TypeError.new("`to' argument must be a module or an object with #{delegated_method_name} defined in a module")
|
|
83
|
-
end
|
|
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")
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
def method_module
|
|
@@ -92,13 +91,12 @@ module Casting
|
|
|
92
91
|
|
|
93
92
|
def delegated_method
|
|
94
93
|
if Module === attendant
|
|
95
|
-
attendant
|
|
94
|
+
attendant
|
|
96
95
|
else
|
|
97
|
-
attendant.method(delegated_method_name).owner
|
|
98
|
-
end
|
|
96
|
+
attendant.method(delegated_method_name).owner
|
|
97
|
+
end.instance_method(delegated_method_name)
|
|
99
98
|
rescue NameError => e
|
|
100
|
-
raise InvalidAttendant
|
|
99
|
+
raise InvalidAttendant, e.message
|
|
101
100
|
end
|
|
102
|
-
|
|
103
101
|
end
|
|
104
102
|
end
|
data/lib/casting/enum.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
module Casting
|
|
2
2
|
module MethodConsolidator
|
|
3
|
-
def methods(all=true)
|
|
3
|
+
def methods(all = true)
|
|
4
4
|
(super + delegated_methods(all)).uniq
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def public_methods(include_super=true)
|
|
7
|
+
def public_methods(include_super = true)
|
|
8
8
|
(super + delegated_public_methods(include_super)).uniq
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def protected_methods(include_super=true)
|
|
11
|
+
def protected_methods(include_super = true)
|
|
12
12
|
(super + delegated_protected_methods(include_super)).uniq
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def private_methods(include_super=true)
|
|
15
|
+
def private_methods(include_super = true)
|
|
16
16
|
(super + delegated_private_methods(include_super)).uniq
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
-
end
|
|
19
|
+
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "casting/method_consolidator"
|
|
2
2
|
|
|
3
3
|
module Casting
|
|
4
4
|
module MissingMethodClient
|
|
5
|
-
|
|
6
5
|
def cast_as(*attendants)
|
|
7
6
|
attendants.each do |attendant|
|
|
8
7
|
validate_attendant(attendant)
|
|
@@ -12,7 +11,7 @@ module Casting
|
|
|
12
11
|
self
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
def uncast(count=1)
|
|
14
|
+
def uncast(count = 1)
|
|
16
15
|
count.times do
|
|
17
16
|
attendant = __delegates__.shift
|
|
18
17
|
attendant.uncast_object(self) if attendant.respond_to?(:uncast_object)
|
|
@@ -20,26 +19,26 @@ module Casting
|
|
|
20
19
|
self
|
|
21
20
|
end
|
|
22
21
|
|
|
23
|
-
def delegated_methods(all=true)
|
|
24
|
-
__delegates__.flat_map{|attendant|
|
|
22
|
+
def delegated_methods(all = true)
|
|
23
|
+
__delegates__.flat_map { |attendant|
|
|
25
24
|
attendant_methods(attendant, all)
|
|
26
25
|
}
|
|
27
26
|
end
|
|
28
27
|
|
|
29
|
-
def delegated_public_methods(include_super=true)
|
|
30
|
-
__delegates__.flat_map{|attendant|
|
|
28
|
+
def delegated_public_methods(include_super = true)
|
|
29
|
+
__delegates__.flat_map { |attendant|
|
|
31
30
|
attendant_public_methods(attendant, include_super)
|
|
32
31
|
}
|
|
33
32
|
end
|
|
34
33
|
|
|
35
|
-
def delegated_protected_methods(include_super=true)
|
|
36
|
-
__delegates__.flat_map{|attendant|
|
|
34
|
+
def delegated_protected_methods(include_super = true)
|
|
35
|
+
__delegates__.flat_map { |attendant|
|
|
37
36
|
attendant_protected_methods(attendant, include_super)
|
|
38
37
|
}
|
|
39
38
|
end
|
|
40
39
|
|
|
41
|
-
def delegated_private_methods(include_super=true)
|
|
42
|
-
__delegates__.flat_map{|attendant|
|
|
40
|
+
def delegated_private_methods(include_super = true)
|
|
41
|
+
__delegates__.flat_map { |attendant|
|
|
43
42
|
attendant_private_methods(attendant, include_super)
|
|
44
43
|
}
|
|
45
44
|
end
|
|
@@ -54,7 +53,7 @@ module Casting
|
|
|
54
53
|
|
|
55
54
|
def method_missing(meth, ...)
|
|
56
55
|
attendant = method_delegate(meth)
|
|
57
|
-
if
|
|
56
|
+
if attendant
|
|
58
57
|
cast(meth, attendant, ...)
|
|
59
58
|
else
|
|
60
59
|
super
|
|
@@ -62,23 +61,23 @@ module Casting
|
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
def respond_to_missing?(meth, *)
|
|
65
|
-
|
|
64
|
+
method_delegate(meth) ? true : super
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
def method_delegate(meth)
|
|
69
|
-
__delegates__.find{|attendant|
|
|
68
|
+
__delegates__.find { |attendant|
|
|
70
69
|
attendant.respond_to?(:method_defined?) && attendant.method_defined?(meth) ||
|
|
71
|
-
|
|
70
|
+
attendant_methods(attendant).include?(meth)
|
|
72
71
|
}
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
def attendant_methods(attendant, all=true)
|
|
74
|
+
def attendant_methods(attendant, all = true)
|
|
76
75
|
collection = attendant_public_methods(attendant) + attendant_protected_methods(attendant)
|
|
77
76
|
collection += attendant_private_methods(attendant) if all
|
|
78
77
|
collection
|
|
79
78
|
end
|
|
80
79
|
|
|
81
|
-
def attendant_public_methods(attendant, include_super=true)
|
|
80
|
+
def attendant_public_methods(attendant, include_super = true)
|
|
82
81
|
if Module === attendant
|
|
83
82
|
attendant.public_instance_methods(include_super)
|
|
84
83
|
else
|
|
@@ -86,7 +85,7 @@ module Casting
|
|
|
86
85
|
end
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
def attendant_protected_methods(attendant, include_super=true)
|
|
88
|
+
def attendant_protected_methods(attendant, include_super = true)
|
|
90
89
|
if Module === attendant
|
|
91
90
|
attendant.protected_instance_methods(include_super)
|
|
92
91
|
else
|
|
@@ -94,7 +93,7 @@ module Casting
|
|
|
94
93
|
end
|
|
95
94
|
end
|
|
96
95
|
|
|
97
|
-
def attendant_private_methods(attendant, include_super=true)
|
|
96
|
+
def attendant_private_methods(attendant, include_super = true)
|
|
98
97
|
if Module === attendant
|
|
99
98
|
attendant.private_instance_methods(include_super)
|
|
100
99
|
else
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Casting
|
|
2
2
|
module MissingMethodClientClass
|
|
3
|
-
|
|
4
3
|
def self.extended(base)
|
|
5
4
|
base.send(:include, InstanceMethods)
|
|
6
5
|
end
|
|
@@ -26,7 +25,7 @@ module Casting
|
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def method_class_delegate(meth)
|
|
29
|
-
__class_delegates__.find{|attendant|
|
|
28
|
+
__class_delegates__.find { |attendant|
|
|
30
29
|
attendant.method_defined?(meth)
|
|
31
30
|
}
|
|
32
31
|
end
|
|
@@ -44,7 +43,7 @@ module Casting
|
|
|
44
43
|
|
|
45
44
|
def __delegates__
|
|
46
45
|
Thread.current[:class_delegates] ||= {}
|
|
47
|
-
Thread.current[:class_delegates][
|
|
46
|
+
Thread.current[:class_delegates][name] ||= []
|
|
48
47
|
end
|
|
49
48
|
end
|
|
50
|
-
end
|
|
49
|
+
end
|
data/lib/casting/null.rb
CHANGED
|
@@ -3,22 +3,28 @@ module Casting
|
|
|
3
3
|
def self.instance_method(*_)
|
|
4
4
|
Empty.instance_method(:null)
|
|
5
5
|
end
|
|
6
|
+
|
|
6
7
|
def self.method_defined?(*_)
|
|
7
8
|
true
|
|
8
9
|
end
|
|
9
10
|
end
|
|
11
|
+
|
|
10
12
|
module Blank
|
|
11
13
|
def self.instance_method(*_)
|
|
12
14
|
Empty.instance_method(:blank)
|
|
13
15
|
end
|
|
16
|
+
|
|
14
17
|
def self.method_defined?(*_)
|
|
15
18
|
true
|
|
16
19
|
end
|
|
17
20
|
end
|
|
21
|
+
|
|
18
22
|
module Empty
|
|
19
|
-
def null(*, &_block)
|
|
23
|
+
def null(*, &_block)
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def blank(*, &_block)
|
|
21
27
|
""
|
|
22
28
|
end
|
|
23
|
-
end
|
|
29
|
+
end
|
|
24
30
|
end
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
module Casting
|
|
2
2
|
module SuperDelegate
|
|
3
|
-
|
|
3
|
+
# Cache regex matchers at module level for better performance
|
|
4
|
+
@casting_library_matcher = nil
|
|
5
|
+
@gem_home_matcher = nil
|
|
6
|
+
@debugging_matcher = /internal:trace_point/
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :casting_library_matcher, :gem_home_matcher, :debugging_matcher
|
|
10
|
+
end
|
|
4
11
|
# Call the method of the same name defined in the next delegate stored in your object
|
|
5
12
|
#
|
|
6
13
|
# Because Casting creates an alternative method lookup path using a collection of delegates,
|
|
@@ -19,7 +26,7 @@ module Casting
|
|
|
19
26
|
#
|
|
20
27
|
# module FormalGreeter
|
|
21
28
|
# include Casting::Super
|
|
22
|
-
#
|
|
29
|
+
#
|
|
23
30
|
# def greet
|
|
24
31
|
# "#{super_delegate}, how do you do?"
|
|
25
32
|
# end
|
|
@@ -29,46 +36,63 @@ module Casting
|
|
|
29
36
|
# some_object.greet #=> 'Hello, how do you do?'
|
|
30
37
|
#
|
|
31
38
|
def super_delegate(mod = :none, *args, **kwargs, &block)
|
|
32
|
-
method_name =
|
|
39
|
+
method_name, method_owner = name_and_owner_of_calling_method(caller_locations)
|
|
33
40
|
owner = (mod unless mod == :none) || method_delegate(method_name)
|
|
34
41
|
|
|
35
42
|
super_delegate_method = unbound_method_from_next_delegate(method_name, owner)
|
|
36
|
-
super_delegate_method.
|
|
43
|
+
super_delegate_method.bind_call(self, *args, **kwargs, &block)
|
|
37
44
|
rescue NameError
|
|
38
|
-
|
|
45
|
+
# Use the method_owner from the call stack for consistent error messages
|
|
46
|
+
owner_name = method_owner || owner
|
|
47
|
+
raise NoMethodError.new("super_delegate: no delegate method `#{owner_name}##{method_name}' for #{inspect} from ")
|
|
39
48
|
end
|
|
40
|
-
|
|
49
|
+
|
|
41
50
|
def unbound_method_from_next_delegate(method_name, *skipped)
|
|
42
51
|
method_delegate_skipping(method_name, *skipped).instance_method(method_name)
|
|
43
52
|
end
|
|
44
|
-
|
|
53
|
+
|
|
45
54
|
def method_delegate_skipping(meth, skipped)
|
|
46
55
|
skipped_index = __delegates__.index(skipped)
|
|
47
|
-
__delegates__[(skipped_index + 1)..__delegates__.length].find{|attendant|
|
|
56
|
+
__delegates__[(skipped_index + 1)..__delegates__.length].find { |attendant|
|
|
48
57
|
attendant_methods(attendant).include?(meth)
|
|
49
58
|
}
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
def calling_location(call_stack)
|
|
53
|
-
call_stack.reject{|line|
|
|
62
|
+
call_stack.reject { |line|
|
|
54
63
|
line.to_s.match? Regexp.union(casting_library_matcher, gem_home_matcher, debugging_matcher)
|
|
55
64
|
}.first
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
def name_of_calling_method(call_stack)
|
|
59
|
-
|
|
68
|
+
method_name, _owner = name_and_owner_of_calling_method(call_stack)
|
|
69
|
+
method_name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def name_and_owner_of_calling_method(call_stack)
|
|
73
|
+
label = calling_location(call_stack).label
|
|
74
|
+
# Ruby 3.4.7+ includes module name in label (e.g., "ModuleName#method_name")
|
|
75
|
+
# Ruby 3.3 and earlier just has method name
|
|
76
|
+
parts = label.split("#")
|
|
77
|
+
if parts.length > 1
|
|
78
|
+
# Ruby 3.4.7+: has owner prefix
|
|
79
|
+
[parts.last.to_sym, parts.first]
|
|
80
|
+
else
|
|
81
|
+
# Ruby 3.3 and earlier: no owner prefix
|
|
82
|
+
[parts.first.to_sym, nil]
|
|
83
|
+
end
|
|
60
84
|
end
|
|
61
|
-
|
|
85
|
+
|
|
62
86
|
def casting_library_matcher
|
|
63
|
-
Regexp.new(Dir.pwd.to_s +
|
|
87
|
+
SuperDelegate.casting_library_matcher ||= Regexp.new(Dir.pwd.to_s + "/lib")
|
|
64
88
|
end
|
|
65
89
|
|
|
66
90
|
def gem_home_matcher
|
|
67
|
-
Regexp.new(ENV[
|
|
91
|
+
SuperDelegate.gem_home_matcher ||= Regexp.new(ENV["GEM_HOME"])
|
|
68
92
|
end
|
|
69
93
|
|
|
70
94
|
def debugging_matcher
|
|
71
|
-
|
|
95
|
+
SuperDelegate.debugging_matcher
|
|
72
96
|
end
|
|
73
97
|
end
|
|
74
98
|
end
|
data/lib/casting/version.rb
CHANGED