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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +46 -0
  3. data/lib/delegates.rb +176 -0
  4. metadata +177 -0
@@ -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
@@ -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).
@@ -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: []