casting 0.7.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c87234df3ca2237a6d2e9d0d6de0f4beb8808cae
4
- data.tar.gz: 114ccf9e9984a2323288b66982465ef7068a4dd0
2
+ SHA256:
3
+ metadata.gz: a7d5101d4c7109e86710609589a9f7dcd4d9d5452e7a452fa29349297325d9a7
4
+ data.tar.gz: 5854e95575b52af1b6d912a5aa3dc18101faa109d9bea387dba59b6bd50b9df7
5
5
  SHA512:
6
- metadata.gz: cc11494446dad4f1b1cd367d9fbd485ad78cf961884348344d076ff9456e1e8235af7be0d8547c1db84c0f953d79428da99d1a649702aa61d7b397d00b776fb9
7
- data.tar.gz: e3d49bea1aaa35a9c08220cd3c07407fcf34657cdd02934ea2f9bd37adb999e3130e37690eebd21b44c884e5a542381b135f207eaaffe3919583877bee70fee4
6
+ metadata.gz: 1b394e9b29860bc90654751bc36296ddf5d67598bddf7a3bcec570d507a31ed62674ddce23cfa25a7a6f8d202664c131fec735f3bd4de2074940f88f3860a9f3
7
+ data.tar.gz: 3e02eb89ad5894b7866569e21aa2efb00e5d00ce9a1d74d1d0865bf7b67303e9a3ddf82333044411a4f1f4f54ff3e0296b42cddaffba1c978609c8f335db2ce7
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2014 Jim Gay
1
+ Copyright (c) 2012-2022 Jim Gay
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,5 @@
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
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)
@@ -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 on the object to the provided attendant. Then the block yields, and an `ensure` block cleans up the stored attendant.
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
 
@@ -22,12 +22,12 @@ module Casting
22
22
  end
23
23
 
24
24
  def delegation(delegated_method_name)
25
- Casting::Delegation.new(delegated_method_name, self)
25
+ Casting::Delegation.prepare(delegated_method_name, self)
26
26
  end
27
27
 
28
- def cast(delegated_method_name, attendant, *args, &block)
28
+ def cast(delegated_method_name, attendant, ...)
29
29
  validate_attendant(attendant)
30
- delegation(delegated_method_name).to(attendant).with(*args, &block).call
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
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # class SomeContext
9
9
  # using Casting::Context
10
- # include Casting::Context
10
+ # extend Casting::Context
11
11
  #
12
12
  # initialize(:some, :thing)
13
13
  # # doing that defines your constructor but would cause it too look for
@@ -18,7 +18,6 @@
18
18
  # # if you want different module names (why would you?) then you'd need
19
19
  # # to do all this:
20
20
  # def initialize(some, thing)
21
- # @assignments = []
22
21
  # assign [some, SomeRole], [thing, OtherRole]
23
22
  # Thread.current[:context] = self
24
23
  # end
@@ -28,9 +27,6 @@
28
27
  # module OtherRole; end
29
28
  # end
30
29
  #
31
- # In order to use this the objects sent into the context contstructor *must*
32
- # `include Casting::Client` so that the `cast` method is available to them
33
- #
34
30
  module Casting
35
31
  module Context
36
32
 
@@ -38,10 +34,16 @@ module Casting
38
34
  base.send(:include, InstanceMethods)
39
35
  end
40
36
 
41
- def initialize(*setup_args)
37
+ def initialize(*setup_args, &block)
42
38
  attr_reader(*setup_args)
43
39
  private(*setup_args)
44
40
 
41
+ if block
42
+ define_method(:__custom_initialize, &block)
43
+ else
44
+ define_method(:__custom_initialize) do; end
45
+ end
46
+
45
47
  mod = Module.new
46
48
  line = __LINE__; string = %<
47
49
  def initialize(#{setup_args.map{|name| "#{name}:" }.join(',')})
@@ -49,6 +51,7 @@ module Casting
49
51
  #{setup_args.map do |name|
50
52
  ["assign(",name,", '",name,"')"].join
51
53
  end.join("\n")}
54
+ __custom_initialize
52
55
  Thread.current[:context] = self
53
56
  end
54
57
  attr_reader :assignments
@@ -62,7 +65,11 @@ module Casting
62
65
  def context
63
66
  self
64
67
  end
65
-
68
+
69
+ def assignments
70
+ @assignments ||= []
71
+ end
72
+
66
73
  # Keep track of objects and their behaviors
67
74
  def assign(object, role_name)
68
75
  instance_variable_set("@#{role_name}", object)
@@ -74,15 +81,19 @@ module Casting
74
81
  end
75
82
 
76
83
  # Execute the behavior from the role on the specifed object
77
- def dispatch(object, method_name, *args, &block)
78
- object.cast(method_name, context.role_implementing(object, method_name), *args, &block)
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
79
90
  end
80
-
91
+
81
92
  # Find the first assigned role which implements a response for the given method name
82
93
  def role_implementing(object, method_name)
83
94
  assigned_roles(object).find{|role| role.method_defined?(method_name) } || raise(NoMethodError, "unknown method '#{method_name}' expected for #{object}")
84
95
  end
85
-
96
+
86
97
  # Get the roles for the given object
87
98
  def assigned_roles(object)
88
99
  assignments.select{|pair|
@@ -96,7 +107,7 @@ module Casting
96
107
  role_name = name.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }
97
108
  self.class.const_get(role_name)
98
109
  rescue NameError
99
- Module
110
+ Module.new
100
111
  end
101
112
  end
102
113
 
@@ -115,11 +126,11 @@ module Casting
115
126
  end
116
127
 
117
128
  # Execute the named method on the object plaing the name role
118
- def tell(role_name, method_name, *args, &block)
129
+ def tell(role_name, method_name, ...)
119
130
  if context == self || context.contains?(self)
120
- context.dispatch(role(role_name), method_name, *args, &block)
131
+ context.dispatch(role(role_name), method_name, ...)
121
132
  end
122
133
  end
123
134
  end
124
135
  end
125
- end
136
+ end
@@ -1,32 +1,104 @@
1
- require 'casting/prepared_delegation'
2
-
3
1
  module Casting
4
- class Delegation
5
2
 
6
- attr_reader :prepared_delegation
7
- private :prepared_delegation
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
- def initialize(delegated_method_name, client)
10
- @prepared_delegation = PreparedDelegation.new(delegated_method_name: delegated_method_name, client: client)
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
- def client
14
- prepared_delegation.client
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
- prepared_delegation.to(object_or_module)
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
- prepared_delegation.with(*args, &block)
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
- prepared_delegation.call(*args)
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
@@ -47,18 +47,15 @@ module Casting
47
47
  private
48
48
 
49
49
  def __delegates__
50
- return @__delegates__ if defined?(@__delegates__)
51
- if frozen?
52
- []
53
- else
54
- @__delegates__ = []
55
- end
50
+ Thread.current[:instance_delegates] ||= {}
51
+ Thread.current[:instance_delegates][object_id] ||= []
52
+ Thread.current[:instance_delegates][object_id]
56
53
  end
57
54
 
58
- def method_missing(meth, *args, &block)
55
+ def method_missing(meth, ...)
59
56
  attendant = method_delegate(meth)
60
57
  if !!attendant
61
- delegate(meth, attendant, *args, &block)
58
+ cast(meth, attendant, ...)
62
59
  else
63
60
  super
64
61
  end
@@ -105,4 +102,4 @@ module Casting
105
102
  end
106
103
  end
107
104
  end
108
- end
105
+ end
@@ -15,7 +15,7 @@ module Casting
15
15
  def method_missing(meth, *args, &block)
16
16
  attendant = method_class_delegate(meth)
17
17
  if !!attendant
18
- delegate(meth, attendant, *args, &block)
18
+ cast(meth, attendant, *args, &block)
19
19
  else
20
20
  super
21
21
  end
data/lib/casting/null.rb CHANGED
@@ -1,17 +1,17 @@
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
@@ -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(caller)
33
- owner = args.first || method_delegate(method_name)
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
- if super_delegate_method.arity == 0
38
- super_delegate_method.bind(self).call
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 name_of_calling_method(call_stack)
51
+
52
+ def calling_location(call_stack)
59
53
  call_stack.reject{|line|
60
- line.to_s =~ casting_library_matcher
61
- }.first.split('`').last.sub("'","").to_sym
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
@@ -1,3 +1,3 @@
1
1
  module Casting
2
- VERSION = '0.7.2'
3
- end
2
+ VERSION = '1.0.0'
3
+ end
data/lib/casting.rb CHANGED
@@ -1,6 +1,9 @@
1
+ require 'casting/version'
1
2
  require 'casting/client'
3
+ require 'casting/enum'
2
4
  require 'casting/super_delegate'
3
5
  require 'casting/null'
6
+ require 'casting/context'
4
7
 
5
8
  module Casting
6
9
 
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
@@ -78,4 +78,4 @@ describe Casting::Client do
78
78
  client.delegate('hello', attendant)
79
79
  }
80
80
  end
81
- end
81
+ end
@@ -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
@@ -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.new('some_method', Object.new)
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.new('some_method', Object.new)
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.new('some_method', TestPerson.new)
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.new('name', client)
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.new('greet', client).to(TestPerson::Greeter)
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.new('class_defined', client).to(Unrelated)
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.new('verbose', client).to(attendant)
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.new('verbose', client).to(attendant)
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
- end
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
@@ -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(ThisWay)}"
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, ThisWay, ThatWay)
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
- end
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,6 +1,7 @@
1
- require "codeclimate-test-reporter"
2
- CodeClimate::TestReporter.start
3
-
1
+ require "simplecov"
2
+ SimpleCov.start do
3
+ add_filter 'test'
4
+ end
4
5
  require 'minitest/autorun'
5
6
  require 'casting'
6
7
 
@@ -31,9 +32,25 @@ class TestPerson
31
32
  def verbose(arg1, arg2)
32
33
  [arg1, arg2].join(',')
33
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
34
47
  end
35
48
  end
36
49
 
50
+ class TestGreeter
51
+ include TestPerson::Greeter
52
+ end
53
+
37
54
  class SubTestPerson < TestPerson
38
55
  def sub_method
39
56
  'sub'
@@ -74,3 +91,7 @@ end
74
91
  def test_person
75
92
  TestPerson.new
76
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.7.2
4
+ version: 1.0.0
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: 2016-07-28 00:00:00.000000000 Z
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.
@@ -31,14 +31,12 @@ files:
31
31
  - lib/casting/missing_method_client.rb
32
32
  - lib/casting/missing_method_client_class.rb
33
33
  - lib/casting/null.rb
34
- - lib/casting/prepared_delegation.rb
35
34
  - lib/casting/super_delegate.rb
36
35
  - lib/casting/version.rb
37
- - test/casting_19_test.rb
38
- - test/casting_20_test.rb
39
36
  - test/casting_test.rb
40
37
  - test/class_refinement_test.rb
41
38
  - test/client_test.rb
39
+ - test/context_test.rb
42
40
  - test/delegation_test.rb
43
41
  - test/method_consolidator_test.rb
44
42
  - test/missing_method_client_test.rb
@@ -50,7 +48,7 @@ homepage: http://github.com/saturnflyer/casting
50
48
  licenses:
51
49
  - MIT
52
50
  metadata: {}
53
- post_install_message:
51
+ post_install_message:
54
52
  rdoc_options: []
55
53
  require_paths:
56
54
  - lib
@@ -58,25 +56,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
56
  requirements:
59
57
  - - ">="
60
58
  - !ruby/object:Gem::Version
61
- version: '0'
59
+ version: '2.7'
62
60
  required_rubygems_version: !ruby/object:Gem::Requirement
63
61
  requirements:
64
62
  - - ">="
65
63
  - !ruby/object:Gem::Version
66
64
  version: '0'
67
65
  requirements: []
68
- rubyforge_project:
69
- rubygems_version: 2.6.6
70
- signing_key:
66
+ rubygems_version: 3.1.6
67
+ signing_key:
71
68
  specification_version: 4
72
69
  summary: Proper method delegation.
73
70
  test_files:
74
71
  - test/test_helper.rb
75
- - test/casting_19_test.rb
76
- - test/casting_20_test.rb
77
72
  - test/casting_test.rb
78
73
  - test/class_refinement_test.rb
79
74
  - test/client_test.rb
75
+ - test/context_test.rb
80
76
  - test/delegation_test.rb
81
77
  - test/method_consolidator_test.rb
82
78
  - test/missing_method_client_test.rb
@@ -1,79 +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_accessor :client, :delegated_method_name, :attendant, :arguments, :block
14
- private :block
15
-
16
- def initialize(**settings, &block)
17
- @delegated_method_name = settings[:delegated_method_name]
18
- @client = settings[:client]
19
- @attendant = settings[:attendant]
20
- @arguments = settings[:arguments]
21
- @block = block
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
- mod = delegated_method.owner
63
- unless mod.is_a?(Class)
64
- mod
65
- end
66
- end
67
-
68
- def delegated_method
69
- if Module === attendant
70
- attendant.instance_method(delegated_method_name)
71
- else
72
- attendant.method(delegated_method_name).owner.instance_method(delegated_method_name)
73
- end
74
- rescue NameError => e
75
- raise InvalidAttendant.new(e.message)
76
- end
77
-
78
- end
79
- end
@@ -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
@@ -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