casting 0.7.0 → 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: 0cca3576453e1acf20799cd80afe5a2e9e4c39cc
4
- data.tar.gz: f892e78276d01554fa159d893507c61fcb4dcf37
2
+ SHA256:
3
+ metadata.gz: a7d5101d4c7109e86710609589a9f7dcd4d9d5452e7a452fa29349297325d9a7
4
+ data.tar.gz: 5854e95575b52af1b6d912a5aa3dc18101faa109d9bea387dba59b6bd50b9df7
5
5
  SHA512:
6
- metadata.gz: 68f289b4009fd5330f856fda1ce71f190ab8317fbf0aa343b9b4244afc6bcdb613be34457c3b98ceb120b620e889511b285099d1006cd6f679be874a6a0a1251
7
- data.tar.gz: 75e7c54b13bad49081fd15597adc4891f3ebfdb5c66b423b0278ecbfd8a3dd48ff22ab1fcc6a2b40b7dae19a29d5e31d1a5a3b1a0986f9250c67e2c3c52c3950
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,8 +1,7 @@
1
1
  # Casting
2
2
 
3
- [![Build Status](https://travis-ci.org/saturnflyer/casting.png?branch=master)](https://travis-ci.org/saturnflyer/casting)
4
3
  [![Code Climate](https://codeclimate.com/github/saturnflyer/casting.png)](https://codeclimate.com/github/saturnflyer/casting)
5
- [![Coverage Status](https://coveralls.io/repos/saturnflyer/casting/badge.png)](https://coveralls.io/r/saturnflyer/casting)
4
+ [![Test Coverage](https://codeclimate.com/github/saturnflyer/casting/badges/coverage.svg)](https://codeclimate.com/github/saturnflyer/casting/coverage)
6
5
  [![Gem Version](https://badge.fury.io/rb/casting.png)](http://badge.fury.io/rb/casting)
7
6
 
8
7
  ## Add behavior to your objects without using extend
@@ -18,7 +17,7 @@ Here's a quick example that you might try in a Rails project:
18
17
  # implement a module that contains information for the request response
19
18
  # and apply it to an object in your system.
20
19
  def show
21
- respond_with user.cast_as(UserRepresenter)
20
+ @user = user.cast_as(UserRepresenter)
22
21
  end
23
22
  ```
24
23
 
@@ -72,6 +71,12 @@ You may also delegate methods without an explicit attendant instance, but provid
72
71
  a module containing the behavior you need to use:
73
72
 
74
73
  ```ruby
74
+ module GreetingModule
75
+ def hello_world
76
+ "hello world"
77
+ end
78
+ end
79
+
75
80
  actor.delegate(:hello_world, GreetingModule)
76
81
  # or
77
82
  actor.delegation(:hello_world).to(GreetingModule).call
@@ -98,6 +103,12 @@ Casting also provides an option to temporarily apply behaviors to an object.
98
103
  Once your class or object is a `Casting::Client` you may send the `delegate_missing_methods` message to it and your object will use `method_missing` to delegate methods to a stored attendant.
99
104
 
100
105
  ```ruby
106
+ class Actor
107
+ include Casting::Client
108
+ delegate_missing_methods
109
+ end
110
+ actor = Actor.new
111
+
101
112
  actor.hello_world #=> NoMethodError
102
113
 
103
114
  Casting.delegating(actor => GreetingModule) do
@@ -109,7 +120,7 @@ actor.hello_world #=> NoMethodError
109
120
 
110
121
  The use of `method_missing` is opt-in. If you don't want that mucking up your method calls, just don't tell it to `delegate_missing_methods`.
111
122
 
112
- Before the block is run in `Casting.delegating`, a collection of delegate objects is set 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
 
@@ -293,22 +304,14 @@ Person.instance_method(:hello) #=> #<UnboundMethod: Person#hello>
293
304
  But if you attempt to use that `UnboundMethod` on an object that is not a `Person` you'll get
294
305
  an error about a type mismatch.
295
306
 
296
- Casting will bind an unbound method to a client object and execute the method as though it is
307
+ Casting will bind an UnboundMethod method to a client object and execute the method as though it is
297
308
  defined on the client object. Any reference to `self` from the method block will refer to the
298
309
  client object.
299
310
 
300
- This behavior is different in Ruby 1.9 vs. 2.x.
301
-
302
- According to [http://rubyspec.org](http://rubyspec.org) the behavior in MRI in 1.9 that allows this to happen is incorrect. In MRI (and JRuby) 1.9 you may unbind methods from an object that has been extended with a module, and bind them to another object of the same type that has *not* been extended with that module.
303
-
304
- Casting uses this as a way to trick the interpreter into using the method where we want it and avoid forever extending the object of concern.
305
-
306
- This changed in Ruby 2.0 and does not work. What does work (and is so much better) in 2.0 is that you may take any method from a module and apply it to any object. This means that Casting doesn't have to perform any tricks to temporarily apply behavior to an object.
307
-
308
- For example, this fails in 1.9, but works in 2.0:
311
+ Rather than define methods on classes, you may take any method from a module and apply it to any object regardless of its class.
309
312
 
310
313
  ```ruby
311
- GreetingModule.instance_method(:hello_world).bind(actor).call
314
+ GreetingModule.instance_method(:hello).bind(actor).call
312
315
  ```
313
316
 
314
317
  Casting provides a convenience for doing this.
@@ -350,12 +353,6 @@ If you are using Bundler, add this line to your application's Gemfile:
350
353
  gem 'casting'
351
354
  ```
352
355
 
353
- If you're using Ruby 1.9, be sure to use a version lower than 0.7
354
-
355
- ```ruby
356
- gem 'casting', '~> 0.6.9'
357
- ```
358
-
359
356
  And then execute:
360
357
 
361
358
  $ bundle
@@ -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
@@ -0,0 +1,136 @@
1
+ # This is an experimental implementation of allowing contextual use of behaviors.
2
+ #
3
+ # This relies on versions of Ruby supporting refinements.
4
+ #
5
+ # You *must* include the following module and use it for refinements, and
6
+ # you *must* set the current context for the thread:
7
+ #
8
+ # class SomeContext
9
+ # using Casting::Context
10
+ # extend Casting::Context
11
+ #
12
+ # initialize(:some, :thing)
13
+ # # doing that defines your constructor but would cause it too look for
14
+ # # modules named Some and Thing
15
+ # module Some; end
16
+ # module Thing; end
17
+ #
18
+ # # if you want different module names (why would you?) then you'd need
19
+ # # to do all this:
20
+ # def initialize(some, thing)
21
+ # assign [some, SomeRole], [thing, OtherRole]
22
+ # Thread.current[:context] = self
23
+ # end
24
+ # attr_reader :some, :thing, :assignments
25
+ #
26
+ # module SomeRole; end
27
+ # module OtherRole; end
28
+ # end
29
+ #
30
+ module Casting
31
+ module Context
32
+
33
+ def self.extended(base)
34
+ base.send(:include, InstanceMethods)
35
+ end
36
+
37
+ def initialize(*setup_args, &block)
38
+ attr_reader(*setup_args)
39
+ private(*setup_args)
40
+
41
+ if block
42
+ define_method(:__custom_initialize, &block)
43
+ else
44
+ define_method(:__custom_initialize) do; end
45
+ end
46
+
47
+ mod = Module.new
48
+ line = __LINE__; string = %<
49
+ def initialize(#{setup_args.map{|name| "#{name}:" }.join(',')})
50
+ @assignments = []
51
+ #{setup_args.map do |name|
52
+ ["assign(",name,", '",name,"')"].join
53
+ end.join("\n")}
54
+ __custom_initialize
55
+ Thread.current[:context] = self
56
+ end
57
+ attr_reader :assignments
58
+ >
59
+ mod.class_eval string, __FILE__, line
60
+ const_set('Initializer', mod)
61
+ include mod
62
+ end
63
+
64
+ module InstanceMethods
65
+ def context
66
+ self
67
+ end
68
+
69
+ def assignments
70
+ @assignments ||= []
71
+ end
72
+
73
+ # Keep track of objects and their behaviors
74
+ def assign(object, role_name)
75
+ instance_variable_set("@#{role_name}", object)
76
+ self.assignments << [object, self.role_for(role_name)]
77
+ end
78
+
79
+ def contains?(obj)
80
+ assignments.map(&:first).include?(obj)
81
+ end
82
+
83
+ # Execute the behavior from the role on the specifed object
84
+ def dispatch(object, method_name, ...)
85
+ if object.respond_to?(:cast)
86
+ object.cast(method_name, context.role_implementing(object, method_name), ...)
87
+ else
88
+ Casting::Delegation.prepare(method_name, object).to(role_implementing(object, method_name)).with(...).call
89
+ end
90
+ end
91
+
92
+ # Find the first assigned role which implements a response for the given method name
93
+ def role_implementing(object, method_name)
94
+ assigned_roles(object).find{|role| role.method_defined?(method_name) } || raise(NoMethodError, "unknown method '#{method_name}' expected for #{object}")
95
+ end
96
+
97
+ # Get the roles for the given object
98
+ def assigned_roles(object)
99
+ assignments.select{|pair|
100
+ pair.first == object
101
+ }.map(&:last)
102
+ end
103
+
104
+ # Get the behavior module for the named role.
105
+ # This role constant for special_person is SpecialPerson.
106
+ def role_for(name)
107
+ role_name = name.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }
108
+ self.class.const_get(role_name)
109
+ rescue NameError
110
+ Module.new
111
+ end
112
+ end
113
+
114
+ refine Object do
115
+ def context
116
+ Thread.current[:context]
117
+ end
118
+
119
+ def context=(obj)
120
+ Thread.current[:context] = obj
121
+ end
122
+
123
+ # Get the object playing a particular role
124
+ def role(role_name)
125
+ context.send(role_name)
126
+ end
127
+
128
+ # Execute the named method on the object plaing the name role
129
+ def tell(role_name, method_name, ...)
130
+ if context == self || context.contains?(self)
131
+ context.dispatch(role(role_name), method_name, ...)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -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,13 +47,15 @@ module Casting
47
47
  private
48
48
 
49
49
  def __delegates__
50
- @__delegates__ ||= []
50
+ Thread.current[:instance_delegates] ||= {}
51
+ Thread.current[:instance_delegates][object_id] ||= []
52
+ Thread.current[:instance_delegates][object_id]
51
53
  end
52
54
 
53
- def method_missing(meth, *args, &block)
55
+ def method_missing(meth, ...)
54
56
  attendant = method_delegate(meth)
55
57
  if !!attendant
56
- delegate(meth, attendant, *args, &block)
58
+ cast(meth, attendant, ...)
57
59
  else
58
60
  super
59
61
  end
@@ -65,6 +67,7 @@ module Casting
65
67
 
66
68
  def method_delegate(meth)
67
69
  __delegates__.find{|attendant|
70
+ attendant.respond_to?(:method_defined?) && attendant.method_defined?(meth) ||
68
71
  attendant_methods(attendant).include?(meth)
69
72
  }
70
73
  end
@@ -99,4 +102,4 @@ module Casting
99
102
  end
100
103
  end
101
104
  end
102
- 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,18 +1,24 @@
1
1
  module Casting
2
2
  module Null
3
- def self.instance_method(name)
3
+ def self.instance_method(*_)
4
4
  Empty.instance_method(:null)
5
5
  end
6
+ def self.method_defined?(*_)
7
+ true
8
+ end
6
9
  end
7
10
  module Blank
8
- def self.instance_method(name)
11
+ def self.instance_method(*_)
9
12
  Empty.instance_method(:blank)
10
13
  end
14
+ def self.method_defined?(*_)
15
+ true
16
+ end
11
17
  end
12
18
  module Empty
13
- def null(*args, &block); end
14
- def blank(*args, &block)
19
+ def null(*, &_block); end
20
+ def blank(*, &_block)
15
21
  ""
16
22
  end
17
23
  end
18
- end
24
+ end
@@ -28,17 +28,12 @@ module Casting
28
28
  # some_object.cast_as(Greeter, FormalGreeter)
29
29
  # some_object.greet #=> 'Hello, how do you do?'
30
30
  #
31
- def super_delegate(*args, &block)
32
- method_name = name_of_calling_method(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.0'
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
@@ -18,4 +18,26 @@ describe Casting::Blank do
18
18
 
19
19
  assert_empty client.delegate('greet', attendant)
20
20
  end
21
+ end
22
+
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
35
+ it "answers to missing methods" do
36
+ client = TestPerson.new
37
+ client.extend(Casting::Client)
38
+ client.delegate_missing_methods
39
+ attendant = Casting::Blank
40
+
41
+ assert_respond_to client.cast_as(attendant), 'xyz'
42
+ end
21
43
  end
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,13 +1,7 @@
1
- require 'simplecov'
1
+ require "simplecov"
2
2
  SimpleCov.start do
3
3
  add_filter 'test'
4
4
  end
5
-
6
- require 'coveralls'
7
- if ENV['COVERALLS']
8
- Coveralls.wear!
9
- end
10
-
11
5
  require 'minitest/autorun'
12
6
  require 'casting'
13
7
 
@@ -38,9 +32,25 @@ class TestPerson
38
32
  def verbose(arg1, arg2)
39
33
  [arg1, arg2].join(',')
40
34
  end
35
+
36
+ def verbose_keywords(key:, word:)
37
+ [key, word].join(',')
38
+ end
39
+
40
+ def verbose_multi_args(arg1, arg2, key:, word:, &block)
41
+ [arg1, arg2, key, word, block&.call].compact.join(',')
42
+ end
43
+
44
+ def verbose_flex(*args, **kwargs, &block)
45
+ [args, kwargs.map{|k,v| "#{k}:#{v}"}, block&.call].flatten.compact.join(',')
46
+ end
41
47
  end
42
48
  end
43
49
 
50
+ class TestGreeter
51
+ include TestPerson::Greeter
52
+ end
53
+
44
54
  class SubTestPerson < TestPerson
45
55
  def sub_method
46
56
  'sub'
@@ -81,3 +91,7 @@ end
81
91
  def test_person
82
92
  TestPerson.new
83
93
  end
94
+
95
+ def casting_person
96
+ test_person.extend(Casting::Client)
97
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: casting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
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: 2014-06-26 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.
@@ -20,62 +20,59 @@ executables: []
20
20
  extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
23
26
  - lib/casting.rb
24
27
  - lib/casting/client.rb
28
+ - lib/casting/context.rb
25
29
  - lib/casting/delegation.rb
26
30
  - lib/casting/method_consolidator.rb
27
31
  - lib/casting/missing_method_client.rb
28
32
  - lib/casting/missing_method_client_class.rb
29
33
  - lib/casting/null.rb
30
- - lib/casting/prepared_delegation.rb
31
34
  - lib/casting/super_delegate.rb
32
35
  - lib/casting/version.rb
33
- - LICENSE
34
- - Rakefile
35
- - README.md
36
- - test/test_helper.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
45
- - test/null_module_test.rb
46
43
  - test/module_cleanup_test.rb
44
+ - test/null_module_test.rb
47
45
  - test/super_test.rb
46
+ - test/test_helper.rb
48
47
  homepage: http://github.com/saturnflyer/casting
49
48
  licenses:
50
49
  - MIT
51
50
  metadata: {}
52
- post_install_message:
51
+ post_install_message:
53
52
  rdoc_options: []
54
53
  require_paths:
55
54
  - lib
56
55
  required_ruby_version: !ruby/object:Gem::Requirement
57
56
  requirements:
58
- - - '>='
57
+ - - ">="
59
58
  - !ruby/object:Gem::Version
60
- version: '0'
59
+ version: '2.7'
61
60
  required_rubygems_version: !ruby/object:Gem::Requirement
62
61
  requirements:
63
- - - '>='
62
+ - - ">="
64
63
  - !ruby/object:Gem::Version
65
64
  version: '0'
66
65
  requirements: []
67
- rubyforge_project:
68
- rubygems_version: 2.0.14
69
- signing_key:
66
+ rubygems_version: 3.1.6
67
+ signing_key:
70
68
  specification_version: 4
71
69
  summary: Proper method delegation.
72
70
  test_files:
73
71
  - test/test_helper.rb
74
- - test/casting_19_test.rb
75
- - test/casting_20_test.rb
76
72
  - test/casting_test.rb
77
73
  - test/class_refinement_test.rb
78
74
  - test/client_test.rb
75
+ - test/context_test.rb
79
76
  - test/delegation_test.rb
80
77
  - test/method_consolidator_test.rb
81
78
  - test/missing_method_client_test.rb
@@ -1,76 +0,0 @@
1
- module Casting
2
-
3
- class MissingAttendant < StandardError
4
- def message
5
- "You must set your attendant object using `to'."
6
- end
7
- end
8
-
9
- class InvalidAttendant < StandardError; end
10
-
11
- class PreparedDelegation
12
-
13
- attr_reader :client
14
- attr_reader :delegated_method_name, :attendant, :arguments, :block
15
- private :delegated_method_name, :attendant, :arguments, :block
16
-
17
- def initialize(settings)
18
- @delegated_method_name = settings[:delegated_method_name]
19
- @client = settings[:client]
20
- @attendant = settings[:attendant]
21
- @arguments = settings[:arguments]
22
- end
23
-
24
- def to(object_or_module)
25
- @attendant = object_or_module
26
- begin
27
- check_valid_type
28
- rescue TypeError
29
- @attendant = method_module || raise
30
- end
31
- self
32
- end
33
-
34
- def with(*args, &block)
35
- @arguments = args
36
- @block = block
37
- self
38
- end
39
-
40
- def call(*args)
41
- @arguments = args unless args.empty?
42
- raise MissingAttendant.new unless attendant
43
-
44
- if arguments
45
- delegated_method.bind(client).call(*arguments, &block)
46
- else
47
- delegated_method.bind(client).call
48
- end
49
- end
50
-
51
- private
52
-
53
- def check_valid_type
54
- begin
55
- !client.nil? && delegated_method.bind(client)
56
- rescue TypeError
57
- raise TypeError.new("`to' argument must be a module or an object with #{delegated_method_name} defined in a module")
58
- end
59
- end
60
-
61
- def method_module
62
- delegated_method.owner unless delegated_method.owner.is_a?(Class)
63
- end
64
-
65
- def delegated_method
66
- if Module === attendant
67
- attendant.instance_method(delegated_method_name)
68
- else
69
- attendant.method(delegated_method_name).owner.instance_method(delegated_method_name)
70
- end
71
- rescue NameError => e
72
- raise InvalidAttendant.new(e.message)
73
- end
74
-
75
- end
76
- end
@@ -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