delegates 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/delegates.svg)](http://badge.fury.io/rb/delegates)
|
4
|
+
![build](https://github.com/zverok/delegates/workflows/CI/badge.svg)
|
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: []
|