delegates 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []