delegates 0.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 +7 -0
- data/README.md +46 -0
- data/lib/delegates.rb +176 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 59b7c5fec2ef33582f5b4563712a3a7d7a750b9a2e4fdfcfb097b880d66927dc
|
4
|
+
data.tar.gz: e49380a8f88e8e48ccd7bd82d98caeee7ffeb7df9000c41a9e8399da0fb9e98b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7f97640dce630c2b2d92c14e82572ffd6515adb6badc0c7f76c36420eaa090918f9417c37a41b1dda8b2e8857dd8285d996c068c7fe56159975ae2adf51f73bc
|
7
|
+
data.tar.gz: 44e85bee9c1f676548510f2fd05453ceda13f485e82a18a180dd53e4e7ee3c0b97f9b93b91a4eabe09f870b57c8102e81ed2830888bff1c33b434d81a32071a4
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Delegates
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/delegates)
|
4
|
+

|
5
|
+
|
6
|
+
This gem is just an extraction of the handy `delegate :method1, :method2, method3, to: :receiver` from ActiveSupport. It seems to be seriously superior to stdlib's [Forwardable](https://ruby-doc.org/stdlib-2.7.1/libdoc/forwardable/rdoc/Forwardable.html), and sometimes I want it in contexts when ActiveSupport and monkey-patching is undesireable.
|
7
|
+
|
8
|
+
Usage:
|
9
|
+
|
10
|
+
```
|
11
|
+
gem install delegates
|
12
|
+
```
|
13
|
+
(or add `gem 'delegates'` to your `Gemfile`).
|
14
|
+
|
15
|
+
Then:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class Employee < Struct.new(:name, :department, :address)
|
19
|
+
# ...
|
20
|
+
extend Delegates
|
21
|
+
delegates :city, :street, to: :address
|
22
|
+
# ...
|
23
|
+
end
|
24
|
+
|
25
|
+
employee = Employee.new(name, department, address)
|
26
|
+
|
27
|
+
employee.city # will call employee.address.city
|
28
|
+
```
|
29
|
+
|
30
|
+
`to:` can be anything evaluatable from inside the class: `:<CONSTANT>`, `:@<instance_variable>`, `'chain.of.calls'` etc.; special names requiring `self` (like `class` method) handled gracefully with just `delegate ..., to: :class`. New methods are defined with `eval`-ing strings, so they are as fast as if manually written.
|
31
|
+
|
32
|
+
Supported options examples (all that ActiveSupport's `Module#delegate` supports):
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
delegate :city, to: :address, prefix: true # defined method would be address_city
|
36
|
+
delegate :city, to: :address, prefix: :adrs # defined method would be adrs_city
|
37
|
+
|
38
|
+
delegate :city, to: :address, private: true # defined method would be private
|
39
|
+
|
40
|
+
delegate :city, to: :address, allow_nil: true
|
41
|
+
```
|
42
|
+
The latter option will handle the `employee.city` call when `employee.address` is `nil` by returning `nil`; otherwise (by default) informative `DelegationError` is raised.
|
43
|
+
|
44
|
+
## Credits
|
45
|
+
|
46
|
+
99.99% of credits should go to Rails users and contributors, who found and and handled miriads of realistic edge cases. I just copied the code (and groomed it a bit for closer to my own style).
|
data/lib/delegates.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Provides a {#delegate} class method to define methods whose calls are delegated to nested objects
|
6
|
+
#
|
7
|
+
# **Examples:**
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# class Greeter
|
11
|
+
# def hello
|
12
|
+
# 'hello'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# def goodbye
|
16
|
+
# 'goodbye'
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# class Foo
|
21
|
+
# def greeter
|
22
|
+
# Greeter.new
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# extend Delegates
|
26
|
+
# delegate :hello, to: :greeter
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Foo.new.hello # => "hello"
|
30
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
31
|
+
# ```
|
32
|
+
#
|
33
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
34
|
+
# by providing them as a symbols:
|
35
|
+
#
|
36
|
+
# ```ruby
|
37
|
+
# class Foo
|
38
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
39
|
+
# @@class_array = [4,5,6,7]
|
40
|
+
#
|
41
|
+
# def initialize
|
42
|
+
# @instance_array = [8,9,10,11]
|
43
|
+
# end
|
44
|
+
# delegate :sum, to: :CONSTANT_ARRAY
|
45
|
+
# delegate :min, to: :@@class_array
|
46
|
+
# delegate :max, to: :@instance_array
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Foo.new.sum # => 6
|
50
|
+
# Foo.new.min # => 4
|
51
|
+
# Foo.new.max # => 11
|
52
|
+
# ```
|
53
|
+
#
|
54
|
+
# See {#delegate} docs for available params.
|
55
|
+
#
|
56
|
+
# The target method must be public, otherwise it will raise `NoMethodError`.
|
57
|
+
#
|
58
|
+
module Delegates
|
59
|
+
# Initial source: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/module/delegation.rb
|
60
|
+
|
61
|
+
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
|
62
|
+
# option is not used.
|
63
|
+
class DelegationError < NoMethodError; end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
RUBY_RESERVED_KEYWORDS =
|
67
|
+
%w[alias and BEGIN begin break case class def defined? do
|
68
|
+
else elsif END end ensure false for if in module next nil not or redo rescue retry
|
69
|
+
return self super then true undef unless until when while yield].freeze
|
70
|
+
|
71
|
+
# @private
|
72
|
+
DELEGATION_RESERVED_KEYWORDS = %w[_ arg args block].freeze
|
73
|
+
|
74
|
+
# @private
|
75
|
+
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
|
76
|
+
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
|
77
|
+
).freeze
|
78
|
+
|
79
|
+
# Note that methods call to exactly once (in case it has side-effects)
|
80
|
+
|
81
|
+
# @private
|
82
|
+
ALLOW_NIL_PATTERN = <<~RUBY
|
83
|
+
def %{method_name}(%{definition})
|
84
|
+
_ = %{to}
|
85
|
+
if !_.nil? || nil.respond_to?(:%{method})
|
86
|
+
_.%{method}(%{definition})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
RUBY
|
90
|
+
|
91
|
+
# @private
|
92
|
+
NO_NIL_PATTERN = <<~RUBY
|
93
|
+
def %{method_name}(%{definition})
|
94
|
+
_ = %{to}
|
95
|
+
_.%{method}(%{definition})
|
96
|
+
rescue NoMethodError => e
|
97
|
+
if _.nil? && e.name == :%{method}
|
98
|
+
raise DelegationError, "%{module}#%{method_name} delegated to %{to}.%{method}, but %{to} is nil: \#{self.inspect}"
|
99
|
+
else
|
100
|
+
raise
|
101
|
+
end
|
102
|
+
end
|
103
|
+
RUBY
|
104
|
+
|
105
|
+
# Defines methods specified in `methods` to delegate their calls to `to`.
|
106
|
+
#
|
107
|
+
# @param methods [Array<String, Symbol>] List of method names to delegate
|
108
|
+
# @param to [String, Symbol] Specifies the target object name, could be anything callable from inside
|
109
|
+
# the class, e.g. `:some_attribute`, `:@any_instance_variable`, `'chain.of.calls`
|
110
|
+
# @param prefix [true, String, Symbol] If `true`, prefixes new method with target method, e.g.
|
111
|
+
# `delegate :name, to: :customer, prefix: true` produces method named `customer_name`; if
|
112
|
+
# set to a string/symbol, uses it as a custom prefix, e.g. `delegate :name, to: :customer, prefix: 'cs'`
|
113
|
+
# produces method named `cs_name`
|
114
|
+
# @param allow_nil [true, false] If set to `true`, then returns `nil` when delegated object
|
115
|
+
# is `nil`; otherwise (default) raises `Delegates::DelegationError`
|
116
|
+
# @param private [true, false] If set to `true`, changes method visibility to private
|
117
|
+
#
|
118
|
+
# @return [Array<Symbol>] Array of method names defined
|
119
|
+
def delegate(*methods, to:, prefix: nil, allow_nil: false, private: false)
|
120
|
+
if prefix == true && /^[^a-z_]/.match?(to)
|
121
|
+
raise ArgumentError,
|
122
|
+
'Can only automatically set the delegation prefix when delegating to a method.'
|
123
|
+
end
|
124
|
+
|
125
|
+
method_prefix = if prefix
|
126
|
+
"#{prefix == true ? to : prefix}_"
|
127
|
+
else
|
128
|
+
''
|
129
|
+
end
|
130
|
+
|
131
|
+
location = caller_locations(1, 1).first
|
132
|
+
file, line = location.path, location.lineno
|
133
|
+
|
134
|
+
to = to.to_s
|
135
|
+
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
|
136
|
+
|
137
|
+
method_defs = []
|
138
|
+
method_names = []
|
139
|
+
|
140
|
+
methods.map(&:to_s).map do |method|
|
141
|
+
method_name = "#{method_prefix}#{method}"
|
142
|
+
method_names << method_name.to_sym
|
143
|
+
|
144
|
+
# Attribute writer methods only accept one argument. Makes sure []=
|
145
|
+
# methods still accept two arguments.
|
146
|
+
definition = if method.match?(/[^\]]=$/)
|
147
|
+
'arg'
|
148
|
+
elsif RUBY_VERSION >= '2.7'
|
149
|
+
'...'
|
150
|
+
else
|
151
|
+
'*args, &block'
|
152
|
+
end
|
153
|
+
|
154
|
+
method_defs <<
|
155
|
+
if allow_nil
|
156
|
+
ALLOW_NIL_PATTERN % {
|
157
|
+
method_name: method_name,
|
158
|
+
definition: definition,
|
159
|
+
method: method,
|
160
|
+
to: to
|
161
|
+
}
|
162
|
+
else
|
163
|
+
NO_NIL_PATTERN % {
|
164
|
+
module: self,
|
165
|
+
method_name: method_name,
|
166
|
+
definition: definition,
|
167
|
+
method: method,
|
168
|
+
to: to
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
module_eval(method_defs.join(';').gsub(/ *\n */m, ';'), file, line)
|
173
|
+
private(*method_names) if private
|
174
|
+
method_names
|
175
|
+
end
|
176
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: delegates
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Shepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubocop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.85.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.85.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop-rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.38.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.38.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-its
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: saharspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.7
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubygems-tasks
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: |2
|
140
|
+
ActiveSupport's delegation syntax is much more convenient than Ruby's stdlib Forwardable.
|
141
|
+
This gem just extracts it as an independent functionality (available on-demand without
|
142
|
+
monkey-patching Module).
|
143
|
+
email: zverok.offline@gmail.com
|
144
|
+
executables: []
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- README.md
|
149
|
+
- lib/delegates.rb
|
150
|
+
homepage: https://github.com/zverok/delegates
|
151
|
+
licenses:
|
152
|
+
- MIT
|
153
|
+
metadata:
|
154
|
+
bug_tracker_uri: https://github.com/zverok/delegates/issues
|
155
|
+
documentation_uri: https://www.rubydoc.info/gems/delegates/
|
156
|
+
homepage_uri: https://github.com/zverok/delegates
|
157
|
+
source_code_uri: https://github.com/zverok/delegates
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 2.4.0
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubygems_version: 3.0.3
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: 'delegate :methods, to: :target, extracted from ActiveSupport'
|
177
|
+
test_files: []
|