casting 0.7.1 → 1.0.1

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: b2c87cd8917d7140a16d55d487f4f0999ee44824
4
- data.tar.gz: 2ffb23af2a79836b0705efe5aa8ac00df0144881
2
+ SHA256:
3
+ metadata.gz: 87f6f44b2f7597c28ab23eb2d02fd06479558ac075f3b6646cd583ea98e5f945
4
+ data.tar.gz: cdf55addbcdbaf848d4991fe8562b5bbeb50ea9b3fde1548f210ebb76d761186
5
5
  SHA512:
6
- metadata.gz: 82e5c33d84c41df11ead6966456bd8b42d0f665c0ba50345ba9b5fa3241dd4b42eaf74a9cd6c08b6212007b7ca1d009f8fe4a6c41a76a1a81ace87e2800be759
7
- data.tar.gz: 502e4254feb758ddff742e3296eed05282b7690d5dda8ab530475da871603db0f3926a31150edcc4140120035ac1f06e059c000b8dbff4eda9337fa81a132bea
6
+ metadata.gz: 71d6eac4b57b571f8c593606f645477402dd9557081ac684c9c5117257e111916b17ec830836cfe9063acae7572aef5b96e20d5219558bf466769119a95b42a1
7
+ data.tar.gz: b81bf24dc30d94cbb520eb302f5173efe2f64b19b062a128071c30833df2f92c4a643b47717fa3092afdc2f20cc15a78631b2fa0fab1a80a8e6e9ed18c7bddc9
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
@@ -0,0 +1,11 @@
1
+ module Casting
2
+ module Enum
3
+ def enum(collection, *behaviors)
4
+ enum = Enumerator.new do |yielder|
5
+ collection.each do |item|
6
+ yielder.yield(item.cast_as(*behaviors))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -100,4 +102,4 @@ module Casting
100
102
  end
101
103
  end
102
104
  end
103
- 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,24 +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?(meth)
6
+ def self.method_defined?(*_)
7
7
  true
8
8
  end
9
9
  end
10
10
  module Blank
11
- def self.instance_method(name)
11
+ def self.instance_method(*_)
12
12
  Empty.instance_method(:blank)
13
13
  end
14
- def self.method_defined?(meth)
14
+ def self.method_defined?(*_)
15
15
  true
16
16
  end
17
17
  end
18
18
  module Empty
19
- def null(*args, &block); end
20
- def blank(*args, &block)
19
+ def null(*, &_block); end
20
+ def blank(*, &_block)
21
21
  ""
22
22
  end
23
23
  end
24
- end
24
+ end
@@ -28,17 +28,12 @@ module Casting
28
28
  # some_object.cast_as(Greeter, FormalGreeter)
29
29
  # some_object.greet #=> 'Hello, how do you do?'
30
30
  #
31
- def super_delegate(*args, &block)
32
- method_name = name_of_calling_method(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.1'
3
- end
2
+ VERSION = '1.0.1'
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
 
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ class Person
4
+ include Casting::Client
5
+ delegate_missing_methods
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+ attr_reader :name
11
+ end
12
+
13
+ class PersonCollection
14
+ include Casting::Enum
15
+
16
+ def initialize(array)
17
+ @array = array
18
+ end
19
+ attr_reader :array
20
+
21
+ def each(*behaviors, &block)
22
+ enum(array, *behaviors).each(&block)
23
+ end
24
+ end
25
+
26
+ module Hello
27
+ def hello
28
+ "Hello, I'm #{name}"
29
+ end
30
+ end
31
+
32
+ describe Casting::Enum, '#enum' do
33
+ let(:people){
34
+ [Person.new('Jim'), Person.new('TJ'), Person.new('Sandi')]
35
+ }
36
+ it "iterates and applies behaviors to each item" do
37
+ client = PersonCollection.new(people)
38
+ assert_equal ["Hello, I'm Jim", "Hello, I'm TJ", "Hello, I'm Sandi"], client.each(Hello).map(&:hello)
39
+ end
40
+ end
data/test/casting_test.rb CHANGED
@@ -1,6 +1,25 @@
1
1
  require 'test_helper'
2
2
 
3
3
  describe Casting, '.delegating' do
4
+ it 'delegates missing methods to object delegates' do
5
+ client = test_person
6
+ client.extend(Casting::Client)
7
+ client.delegate_missing_methods
8
+
9
+ attendant = test_person
10
+ attendant.extend(TestPerson::Greeter)
11
+
12
+ assert_raises(NoMethodError){
13
+ client.greet
14
+ }
15
+ Casting.delegating(client => attendant) do
16
+ assert_equal 'hello', client.greet
17
+ end
18
+ assert_raises(NoMethodError){
19
+ client.greet
20
+ }
21
+ end
22
+
4
23
  it 'delegates missing methods for the objects inside the block' do
5
24
  client = BlockTestPerson.new('Jim')
6
25
  verbose_client = BlockTestPerson.new('Amy')
data/test/client_test.rb CHANGED
@@ -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
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+
3
+ describe Casting::Client do
4
+ it 'will not attempt to alter a frozen client' do
5
+ client = TestPerson.new
6
+ client.extend(Casting::Client)
7
+ client.delegate_missing_methods
8
+
9
+ client.freeze
10
+
11
+ err = expect{ client.greet }.must_raise(NoMethodError)
12
+ expect(err.message).must_match(/undefined method \`greet'/)
13
+ end
14
+ end
@@ -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,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.1
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-27 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,65 +20,67 @@ executables: []
20
20
  extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
23
26
  - lib/casting.rb
24
27
  - lib/casting/client.rb
28
+ - lib/casting/context.rb
25
29
  - lib/casting/delegation.rb
30
+ - lib/casting/enum.rb
26
31
  - lib/casting/method_consolidator.rb
27
32
  - lib/casting/missing_method_client.rb
28
33
  - lib/casting/missing_method_client_class.rb
29
34
  - lib/casting/null.rb
30
- - lib/casting/prepared_delegation.rb
31
35
  - lib/casting/super_delegate.rb
32
36
  - lib/casting/version.rb
33
- - LICENSE
34
- - Rakefile
35
- - README.md
36
- - test/test_helper.rb
37
- - test/casting_19_test.rb
38
- - test/casting_20_test.rb
37
+ - test/casting_enum_test.rb
39
38
  - test/casting_test.rb
40
39
  - test/class_refinement_test.rb
41
40
  - test/client_test.rb
41
+ - test/context_test.rb
42
42
  - test/delegation_test.rb
43
+ - test/frozen_client_test.rb
43
44
  - test/method_consolidator_test.rb
44
45
  - test/missing_method_client_test.rb
45
- - test/null_module_test.rb
46
46
  - test/module_cleanup_test.rb
47
+ - test/null_module_test.rb
47
48
  - test/super_test.rb
49
+ - test/test_helper.rb
48
50
  homepage: http://github.com/saturnflyer/casting
49
51
  licenses:
50
52
  - MIT
51
53
  metadata: {}
52
- post_install_message:
54
+ post_install_message:
53
55
  rdoc_options: []
54
56
  require_paths:
55
57
  - lib
56
58
  required_ruby_version: !ruby/object:Gem::Requirement
57
59
  requirements:
58
- - - '>='
60
+ - - ">="
59
61
  - !ruby/object:Gem::Version
60
- version: '0'
62
+ version: '2.7'
61
63
  required_rubygems_version: !ruby/object:Gem::Requirement
62
64
  requirements:
63
- - - '>='
65
+ - - ">="
64
66
  - !ruby/object:Gem::Version
65
67
  version: '0'
66
68
  requirements: []
67
- rubyforge_project:
68
- rubygems_version: 2.0.14
69
- signing_key:
69
+ rubygems_version: 3.2.27
70
+ signing_key:
70
71
  specification_version: 4
71
72
  summary: Proper method delegation.
72
73
  test_files:
73
74
  - test/test_helper.rb
74
- - test/casting_19_test.rb
75
- - test/casting_20_test.rb
75
+ - test/casting_enum_test.rb
76
76
  - test/casting_test.rb
77
77
  - test/class_refinement_test.rb
78
78
  - test/client_test.rb
79
+ - test/context_test.rb
79
80
  - test/delegation_test.rb
81
+ - test/frozen_client_test.rb
80
82
  - test/method_consolidator_test.rb
81
83
  - test/missing_method_client_test.rb
82
- - test/null_module_test.rb
83
84
  - test/module_cleanup_test.rb
85
+ - test/null_module_test.rb
84
86
  - test/super_test.rb
@@ -1,76 +0,0 @@
1
- module Casting
2
-
3
- class MissingAttendant < StandardError
4
- def message
5
- "You must set your attendant object using `to'."
6
- end
7
- end
8
-
9
- class InvalidAttendant < StandardError; end
10
-
11
- class PreparedDelegation
12
-
13
- attr_reader :client
14
- attr_reader :delegated_method_name, :attendant, :arguments, :block
15
- private :delegated_method_name, :attendant, :arguments, :block
16
-
17
- def initialize(settings)
18
- @delegated_method_name = settings[:delegated_method_name]
19
- @client = settings[:client]
20
- @attendant = settings[:attendant]
21
- @arguments = settings[:arguments]
22
- end
23
-
24
- def to(object_or_module)
25
- @attendant = object_or_module
26
- begin
27
- check_valid_type
28
- rescue TypeError
29
- @attendant = method_module || raise
30
- end
31
- self
32
- end
33
-
34
- def with(*args, &block)
35
- @arguments = args
36
- @block = block
37
- self
38
- end
39
-
40
- def call(*args)
41
- @arguments = args unless args.empty?
42
- raise MissingAttendant.new unless attendant
43
-
44
- if arguments
45
- delegated_method.bind(client).call(*arguments, &block)
46
- else
47
- delegated_method.bind(client).call
48
- end
49
- end
50
-
51
- private
52
-
53
- def check_valid_type
54
- begin
55
- !client.nil? && delegated_method.bind(client)
56
- rescue TypeError
57
- raise TypeError.new("`to' argument must be a module or an object with #{delegated_method_name} defined in a module")
58
- end
59
- end
60
-
61
- def method_module
62
- delegated_method.owner unless delegated_method.owner.is_a?(Class)
63
- end
64
-
65
- def delegated_method
66
- if Module === attendant
67
- attendant.instance_method(delegated_method_name)
68
- else
69
- attendant.method(delegated_method_name).owner.instance_method(delegated_method_name)
70
- end
71
- rescue NameError => e
72
- raise InvalidAttendant.new(e.message)
73
- end
74
-
75
- end
76
- end
@@ -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