casting 0.7.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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