eco-helpers 3.0.27 → 3.0.28

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/eco-helpers.gemspec +1 -0
  4. data/lib/eco/api/common/class_helpers.rb +1 -136
  5. data/lib/eco/api/common/loaders/config/cli.rb +1 -1
  6. data/lib/eco/api/common/loaders/config/session.rb +1 -1
  7. data/lib/eco/api/common/loaders/config/workflow.rb +1 -1
  8. data/lib/eco/api/common/loaders/config.rb +2 -5
  9. data/lib/eco/api/common/loaders/use_case/target_model.rb +1 -1
  10. data/lib/eco/api/common/loaders/use_case/type.rb +1 -1
  11. data/lib/eco/api/common/people/supervisor_helpers.rb +3 -1
  12. data/lib/eco/api/common/session/logger/channels.rb +1 -0
  13. data/lib/eco/api/common/session/sftp.rb +9 -2
  14. data/lib/eco/api/session/batch/job/sets.rb +1 -0
  15. data/lib/eco/api/session/batch/job/type.rb +1 -0
  16. data/lib/eco/api/session/batch/launcher/valid_methods.rb +3 -2
  17. data/lib/eco/api/usecases/base_case/model.rb +2 -1
  18. data/lib/eco/api/usecases/base_case/type.rb +2 -1
  19. data/lib/eco/api/usecases/base_io/validations.rb +2 -1
  20. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +3 -2
  21. data/lib/eco/api/usecases/lib/files/file_pattern.rb +0 -2
  22. data/lib/eco/api/usecases/lib/files/sftp.rb +7 -4
  23. data/lib/eco/api/usecases/samples/drivers/sftp_sample.rb +3 -1
  24. data/lib/eco/data/files/helpers.rb +4 -2
  25. data/lib/eco/data/fuzzy_match.rb +4 -2
  26. data/lib/eco/data/hashes/diff_result/meta.rb +2 -1
  27. data/lib/eco/data/locations/node_diff/accessors.rb +2 -1
  28. data/lib/eco/language/delegation/chainable_delegator.rb +18 -0
  29. data/lib/eco/language/delegation/delegating_missing.rb +104 -0
  30. data/lib/eco/language/delegation/delegating_missing_const.rb +53 -0
  31. data/lib/eco/language/delegation/delegating_missing_on_class.rb +53 -0
  32. data/lib/eco/language/delegation/{const_delegator.rb → for_delegator/const_delegator.rb} +8 -6
  33. data/lib/eco/language/delegation/{const_lookup_hooks.rb → for_delegator/const_lookup_hooks.rb} +34 -16
  34. data/lib/eco/language/delegation/{delegated_class.rb → for_delegator/delegated_class.rb} +6 -19
  35. data/lib/eco/language/delegation/for_delegator.rb +11 -0
  36. data/lib/eco/language/delegation.rb +5 -3
  37. data/lib/eco/language/klass/builder.rb +29 -0
  38. data/lib/eco/language/klass/helpers_built.rb +9 -0
  39. data/lib/eco/language/klass/hierarchy.rb +34 -0
  40. data/lib/eco/language/klass/inheritable_class_vars.rb +45 -0
  41. data/lib/eco/language/klass/naming.rb +21 -0
  42. data/lib/eco/language/klass/resolver.rb +30 -0
  43. data/lib/eco/language/klass/when_inherited.rb +10 -13
  44. data/lib/eco/language/klass.rb +6 -0
  45. data/lib/eco/language/methods.rb +0 -1
  46. data/lib/eco/language/strings/underscore.rb +17 -0
  47. data/lib/eco/language/strings.rb +8 -0
  48. data/lib/eco/language.rb +1 -0
  49. data/lib/eco/version.rb +1 -1
  50. metadata +32 -6
  51. data/lib/eco/language/methods/delegate_missing.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc0d97c18d012621fea4cc9df6f7f3af54ba2a29da761c123c2502e7708f2267
4
- data.tar.gz: ea83680a1a302da67ecdded6a0ea22043effe995d496c88289e7f55d54c9bde1
3
+ metadata.gz: 3f36ef48b8d70b2ac8b50e32a823d99d8bec5e32a76975357cf2298a6247a694
4
+ data.tar.gz: 60c6b240668df642d9bba41bc67011a02f7602e143aaabc2e4a25d2f9aa5d5dc
5
5
  SHA512:
6
- metadata.gz: 2c0c45e02b3d5ee47fe5de33d38a4b1ade68eb759d1134b1df6c0880cbbba128f78299b2687af9177ca858a353e750339c2a85fc9731f78d194fd347faf329f0
7
- data.tar.gz: 96e499c457c58667a29fffd0f0779bc986b6fc5635c1de4a5d0be5526778bd6065f6305f94c39f63badebfd8474ac76c5c780c766d28ae4824d1743ddf49e986
6
+ metadata.gz: 70393a40919bf80751385734885c88914a80acc47a80d5a068be431b4abd2e9677d8e1707dbeaf8510de49dc519d00441df3ffe2bed4ee8f50663895caf2e778
7
+ data.tar.gz: 44cc94d912bdcee5ec8140ce8d78a0b2a10feb8c609944147446e4337641ca53394ee3e798b17d970cae887b07313ed511a4840e47af7fa903595311a5923eba
data/CHANGELOG.md CHANGED
@@ -2,7 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [3.0.27] - 2025-03-xx
5
+ ## [3.0.29] - 2025-03-xx
6
+
7
+ ### Added
8
+
9
+ ### Changed
10
+
11
+ ### Fixed
12
+
13
+ ## [3.0.28] - 2025-03-31
14
+
15
+ ### Added
16
+
17
+ - `Language::Delegation::DelegatingMissing` (rails alike, but ending with `ing`).
18
+ - `Language::Delegation::DelegatingMissingConst`.
19
+ - `Language::Delegation::ChainableDelegator`
20
+ - `Language::Strings::Underscore`
21
+ - `Eco::API::UseCases::Lib::Files::Sftp`
22
+ - **Added** named argument `only_latest` to fetch only latest file (**newest**).
23
+
24
+ ### Changed
25
+
26
+ - `Eco::API::UseCases::Lib::Files::Sftp#local_folder`
27
+ - **Remove** default to `.` (current/root directory).
28
+ - **Moved** `Language::Methods::DelegateMissing` to `Language::Delegation::DelegatingMissingOnClass`
29
+ - **Renamed** `::delegate_missing_to` to `::delegating_missing_on_class_to`
30
+ - **Moved** `DelegatedClass`, `ConstDelegator` and `ConstLookupHooks` from `Language::Delegation` to `Language::Delegation::ForDelegator`
31
+ - `DelegatedClass`
32
+ - **Moved** `#initialize` to the **new** `ChainableDelegator` sample
33
+ - **Moved** requirement for base to be a `Delegator` or subclass thereof.
34
+ - The requirement is now part of `ConstLookupHooks`, as this particular concern only makes sense on `Delegator` subclasses.
35
+ - **Moved** (by keeping backwards compatibility) `Eco::API::Common::ClassHelpers` to decoupled helpers in `Eco::Language::Klass`
36
+ - **Manage Exception** on `Eco::API::Common::Session::SFTP#upload`
37
+ - **Added** remote `dest_file` feedback
38
+
39
+ ### Fixed
40
+
41
+ - **Remove** recursive call on `Lib::Files::FilePattern#file_pattern_const`
42
+
43
+ ## [3.0.27] - 2025-03-29
6
44
 
7
45
  ### Added
8
46
 
data/eco-helpers.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  #spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
+ spec.add_development_dependency 'byebug'
30
31
  spec.add_development_dependency 'rake', '>= 13.0.3', '< 14'
31
32
  spec.add_development_dependency 'redcarpet', '>= 3.6.0', '< 4'
32
33
  spec.add_development_dependency 'rspec', '>= 3.12.0', '< 4'
@@ -2,142 +2,7 @@ module Eco
2
2
  module API
3
3
  module Common
4
4
  module ClassHelpers
5
- # Creates a class and instance object methods with name `name` to resolve `klass` name
6
- def class_resolver(name, klass)
7
- define_singleton_method(name) { resolve_class(klass) }
8
- define_method(name) { self.class.resolve_class(klass) }
9
- end
10
-
11
- # With given a `klass` name it resolves to an actual `Class`
12
- # @return [Class] the class that was being searched by name `klass`.
13
- def resolve_class(klass, exception: true)
14
- @resolved ||= {}
15
- @resolved[klass] ||=
16
- case klass
17
- when Class
18
- klass
19
- when String
20
- begin
21
- Kernel.const_get(klass)
22
- rescue NameError
23
- raise if exception
24
- end
25
- when Symbol
26
- resolve_class(send(klass))
27
- else
28
- raise "Unknown class: #{klass}" if exception
29
- end
30
- end
31
-
32
- # Helper to normalize `key` into a correct `ruby` **constant name**
33
- # @param key [String, Symbol] to be normalized
34
- # @return [String] a correct constant name
35
- def to_constant(key)
36
- key.to_s.strip.split(/[\-\_ ]/i).compact.map do |str|
37
- str.slice(0).upcase + str.slice(1..-1).downcase
38
- end.join
39
- end
40
-
41
- # Helper to create an instance variable `name`
42
- # @param [String, Symbol] the name of the variable
43
- # @reutrn [String] the name of the created instance variable
44
- def instance_variable_name(name)
45
- str = name.to_s
46
- str = "@#{str}" unless str.start_with?("@")
47
- str
48
- end
49
-
50
- # If the class for `name` exists, it returns it. Otherwise it generates it.
51
- # @param name [String, Symbol] the name of the new class
52
- # @param inherits [Class] the parent class to _inherit_ from
53
- # @param parent_space [String] parent namespace of the generated class, if not given: `self`
54
- # @yield [child_class] configure the new class
55
- # @yieldparam child_class [Class] the new class
56
- # @return [Class] the new generated class
57
- def new_class(name, inherits:, parent_space: nil)
58
- name = name.to_sym.freeze
59
- class_name = to_constant(name)
60
- parent_space = parent_space ? resolve_class(parent_space) : self
61
- full_class_name = "#{parent_space}::#{class_name}"
62
-
63
- unless (target_class = resolve_class(full_class_name, exception: false))
64
- target_class = Class.new(inherits)
65
- parent_space.const_set class_name, target_class
66
- end
67
-
68
- target_class.tap do |klass|
69
- yield(klass) if block_given?
70
- end
71
- end
72
-
73
- # Finds all child classes of the current class.
74
- # @param parent_class [Class] the parent class we want to find children of.
75
- # @param direct [Boolean] it will only include direct child classes.
76
- # @param scope [nil, Array] to only look for descendants among the ones in `scope`.
77
- # @return [Arrary<Class>] the child classes in hierarchy order.
78
- def descendants(parent_class: self, direct: false, scope: nil)
79
- scope ||= ObjectSpace.each_object(::Class)
80
- return [] if scope.empty?
81
- scope.select do |klass|
82
- klass < parent_class
83
- end.sort do |k_1, k_2|
84
- next -1 if k_2 < k_1
85
- next 1 if k_1 < k_2
86
- 0
87
- end.tap do |siblings|
88
- if direct
89
- siblings.reject! do |si|
90
- siblings.any? {|s| si < s}
91
- end
92
- end
93
- end
94
- end
95
-
96
- # @param parent_class [Class] the parent class we want to find children of.
97
- # @param direct [Boolean] it will only include direct child classes.
98
- # @return [Boolean] `true` if the current class has child classes, and `false` otherwise.
99
- def descendants?(parent_class: self, direct: false)
100
- descendants(parent_class: parent_class, direct: direct).length.positive?
101
- end
102
-
103
- # Keeps track on class instance variables that should be inherited by child classes.
104
- # @note
105
- # - subclasses will inherit the value as is at that moment
106
- # - any change afterwards will be only on the specific class (in line with class instance variables)
107
- # - adapted from https://stackoverflow.com/a/10729812/4352306
108
- # TODO: this separates the logic of the method to the instance var.
109
- # Think if would be possible to join them somehow.
110
- def inheritable_class_vars(*vars)
111
- @inheritable_class_vars ||= [:inheritable_class_vars]
112
- @inheritable_class_vars += vars
113
- end
114
-
115
- # Builds the attr_reader and attr_writer of `attrs` and registers
116
- # the associated instance variable as inheritable.
117
- def inheritable_attrs(*attrs)
118
- attrs.each do |attr|
119
- class_eval %(
120
- class << self; attr_accessor :#{attr} end
121
- ), __FILE__, __LINE__ - 2
122
- end
123
-
124
- inheritable_class_vars(*attrs)
125
- end
126
-
127
- # This callback method is called whenever a subclass of the current class is created.
128
- # @note
129
- # - values of the instance variables are copied as they are (no dups or clones)
130
- # - the above means: avoid methods that change the state of the mutable object on it
131
- # - mutating methods would reflect the changes on other classes as well
132
- # - therefore, `freeze` will be called on the values that are inherited.
133
- def inherited(subclass)
134
- super
135
- inheritable_class_vars.each do |var|
136
- instance_var = instance_variable_name(var)
137
- value = instance_variable_get(instance_var)
138
- subclass.instance_variable_set(instance_var, value.freeze)
139
- end
140
- end
5
+ include Eco::Language::Klass::HelpersBuilt
141
6
  end
142
7
  end
143
8
  end
@@ -5,5 +5,5 @@ class Eco::API::Common::Loaders::CliConfig < Eco::API::Common::Loaders::Config
5
5
  end
6
6
  end
7
7
 
8
- delegate_missing_to :config
8
+ delegating_missing_on_class_to :config
9
9
  end
@@ -8,5 +8,5 @@ class Eco::API::Common::Loaders::Session < Eco::API::Common::Loaders::Config
8
8
  end
9
9
  end
10
10
 
11
- delegate_missing_to :workflow
11
+ delegating_missing_on_class_to :workflow
12
12
  end
@@ -9,7 +9,7 @@ class Eco::API::Common::Loaders::Workflow < Eco::API::Common::Loaders::Config
9
9
 
10
10
  extend CasesExtension
11
11
 
12
- delegate_missing_to :workflow
12
+ delegating_missing_on_class_to :workflow
13
13
  end
14
14
 
15
15
  require_relative 'workflow/mailer'
@@ -1,8 +1,7 @@
1
1
  class Eco::API::Common::Loaders::Config
2
- extend Eco::API::Common::ClassHelpers
3
- extend Eco::Language::Methods::DelegateMissing
2
+ include Eco::Language::Delegation::DelegatingMissingOnClass
4
3
 
5
- inheritable_class_vars :delegate_missing_to
4
+ delegating_missing_on_class_to :config
6
5
 
7
6
  class << self
8
7
  # To create samples of configurations
@@ -29,8 +28,6 @@ class Eco::API::Common::Loaders::Config
29
28
  config.active_enviro
30
29
  end
31
30
  end
32
-
33
- delegate_missing_to :config
34
31
  end
35
32
 
36
33
  require_relative 'config/cli'
@@ -4,7 +4,7 @@ class Eco::API::Common::Loaders::UseCase
4
4
  def included(base)
5
5
  super
6
6
 
7
- base.extend(ClassMethods)
7
+ base.extend ClassMethods
8
8
  end
9
9
  end
10
10
 
@@ -4,7 +4,7 @@ class Eco::API::Common::Loaders::UseCase
4
4
  def included(base)
5
5
  super
6
6
 
7
- base.extend(ClassMethods)
7
+ base.extend ClassMethods
8
8
  end
9
9
  end
10
10
 
@@ -8,7 +8,9 @@ module Eco
8
8
  # * `id`
9
9
  module SupervisorHelpers
10
10
  def self.included(base)
11
- base.send(:include, ClassMethods)
11
+ super
12
+
13
+ base.send :include, ClassMethods
12
14
  end
13
15
 
14
16
  module ClassMethods
@@ -9,6 +9,7 @@ module Eco
9
9
  class << self
10
10
  def included(base)
11
11
  super
12
+
12
13
  base.extend ClassMethods
13
14
  end
14
15
  end
@@ -118,6 +118,7 @@ module Eco
118
118
  end
119
119
 
120
120
  # Upload a file to the specific `remote_folder`
121
+ # @return [String, FalseClass] on success returns the remote file path.
121
122
  def upload(local_file, remote_folder:, gid: nil)
122
123
  return false unless local_file && File.exist?(local_file)
123
124
 
@@ -133,9 +134,15 @@ module Eco
133
134
  end
134
135
 
135
136
  log(:info) {
136
- "Uploaded '#{local_file}' (#{res})"
137
+ "Uploaded '#{local_file}' as '#{dest_file}' (#{res})"
137
138
  }
138
- true
139
+
140
+ dest_file
141
+ rescue StandardError
142
+ log(:error) {
143
+ "Could not upload file '#{dest_file}' (#{res})"
144
+ }
145
+ raise
139
146
  end
140
147
 
141
148
  private
@@ -5,6 +5,7 @@ class Eco::API::Session::Batch::Job
5
5
  class << self
6
6
  def included(base)
7
7
  super
8
+
8
9
  base.extend ClassMethods
9
10
  end
10
11
  end
@@ -5,6 +5,7 @@ class Eco::API::Session::Batch::Job
5
5
  class << self
6
6
  def included(base)
7
7
  super
8
+
8
9
  base.extend ClassMethods
9
10
  end
10
11
  end
@@ -8,8 +8,9 @@ module Eco
8
8
 
9
9
  def self.included(base)
10
10
  super
11
- base.extend(ClassMethods)
12
- base.send(:include, InstanceMethods)
11
+
12
+ base.extend ClassMethods
13
+ base.send :include, InstanceMethods
13
14
  end
14
15
 
15
16
  module ClassMethods
@@ -17,7 +17,8 @@ class Eco::API::UseCases::BaseCase
17
17
  class << self
18
18
  def included(base)
19
19
  super
20
- base.extend(ClassMethods)
20
+
21
+ base.extend ClassMethods
21
22
  end
22
23
  end
23
24
 
@@ -21,7 +21,8 @@ class Eco::API::UseCases::BaseCase
21
21
  class << self
22
22
  def included(base)
23
23
  super
24
- base.extend(ClassMethods)
24
+
25
+ base.extend ClassMethods
25
26
  end
26
27
  end
27
28
 
@@ -29,7 +29,8 @@ class Eco::API::UseCases::BaseIO
29
29
  class << self
30
30
  def included(base)
31
31
  super
32
- base.extend(ClassMethods)
32
+
33
+ base.extend ClassMethods
33
34
  end
34
35
  end
35
36
 
@@ -4,8 +4,9 @@ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
4
4
  class << self
5
5
  def included(base)
6
6
  super
7
- base.extend(ClassMethods)
8
- base.send(:include, DiffSortable)
7
+
8
+ base.extend ClassMethods
9
+ base.send :include, DiffSortable
9
10
  end
10
11
  end
11
12
 
@@ -29,8 +29,6 @@ module Eco::API::UseCases::Lib::Files
29
29
  rescue NameError
30
30
  self.class.const_get(fpc)
31
31
  end
32
- elsif respond_to?(:file_pattern, true)
33
- file_pattern
34
32
  elsif self.class.const_defined?(:FILE_PATTERN)
35
33
  self.class::FILE_PATTERN
36
34
  end
@@ -8,6 +8,7 @@ module Eco::API::UseCases::Lib::Files
8
8
 
9
9
  private
10
10
 
11
+ # @return [String, FalseClass] the path of the remote file or `false` if failed.
11
12
  def upload(local_file, subfolder: remote_subfolder, gid: sftp_group_id)
12
13
  sftp_session.upload(
13
14
  local_file,
@@ -34,15 +35,19 @@ module Eco::API::UseCases::Lib::Files
34
35
  subfolder: remote_subfolder,
35
36
  pattern: nil,
36
37
  local_folder: self.local_folder,
38
+ only_latest: false,
37
39
  &block
38
40
  )
39
41
  remote_files = with_remote_files(subfolder: subfolder, pattern: pattern)
40
- return [] if remote_files.empty?
42
+
43
+ return [] if remote_files.empty?
44
+ remote_files = [remote_files.first] if only_latest
41
45
 
42
46
  file_names = remote_files.map {|file| to_remote_path(file.name, subfolder: subfolder)}
43
47
 
44
48
  log(:info) {
45
- msg = "Getting the following files into the local folder '#{local_folder}':\n"
49
+ msg = "Getting the following files into the local folder "
50
+ msg << "'#{local_folder || '.'}':\n"
46
51
  msg << ' * '
47
52
  msg << file_names.join("\n * ")
48
53
  msg
@@ -118,8 +123,6 @@ module Eco::API::UseCases::Lib::Files
118
123
  local_dir
119
124
  elsif self.class.const_defined?(:LOCAL_FOLDER)
120
125
  self.class::LOCAL_FOLDER
121
- else
122
- '.'
123
126
  end
124
127
  end
125
128
 
@@ -37,7 +37,9 @@ class Eco::API::UseCases::Samples::Drivers::Sftp < Eco::API::Common::Loaders::Us
37
37
 
38
38
  file_names = files.map {|file| to_remote_path(file.name)}
39
39
 
40
- puts "Getting the following files into the local folder '#{local_folder}':"
40
+ msg = "Getting the following files into the local folder "
41
+ msg << "'#{local_folder || '.'}':"
42
+ puts msg
41
43
  puts file_names
42
44
 
43
45
  sftp.download(file_names, local_folder: local_folder)
@@ -6,8 +6,10 @@ module Eco
6
6
 
7
7
  class << self
8
8
  def included(base)
9
- base.send(:include, InstanceMethods)
10
- base.extend(ClassMethods)
9
+ super
10
+
11
+ base.send :include, InstanceMethods
12
+ base.extend ClassMethods
11
13
  end
12
14
  end
13
15
 
@@ -11,8 +11,10 @@ module Eco
11
11
 
12
12
  class << self
13
13
  def included(base)
14
- base.send(:include, InstanceMethods)
15
- base.extend(ClassMethods)
14
+ super
15
+
16
+ base.send :include, InstanceMethods
17
+ base.extend ClassMethods
16
18
  end
17
19
  end
18
20
 
@@ -5,7 +5,8 @@ module Eco
5
5
  module Meta
6
6
  class << self
7
7
  def included(base)
8
- super(base)
8
+ super
9
+
9
10
  base.extend Eco::Language::Models::ClassHelpers
10
11
  base.extend ClassMethods
11
12
  base.inheritable_class_vars :key, :compared_attrs, :case_sensitive
@@ -2,7 +2,8 @@ class Eco::Data::Locations::NodeDiff
2
2
  module Accessors
3
3
  class << self
4
4
  def included(base)
5
- super(base)
5
+ super
6
+
6
7
  base.extend Eco::Language::Models::ClassHelpers
7
8
  base.extend ClassMethods
8
9
  base.inheritable_class_vars :exposed_attrs
@@ -0,0 +1,18 @@
1
+ module Eco::Language::Delegation
2
+ class ChainableDelegator < SimpleDelegator
3
+ include ForDelegator::DelegatedClass
4
+ include ForDelegator::ConstDelegator
5
+ include ForDelegator::ConstLookupHooks
6
+
7
+ # It allows to chain delegators, when the first parameter is an
8
+ # instance of `delegated_class`.
9
+ # @note it also allows to create an instance of `delegated_class`
10
+ # on initialization.
11
+ def initialize(*args, **kargs, &block)
12
+ obj = args.first
13
+ obj = new(*args, **kargs, &block) unless of_kind?(obj)
14
+
15
+ super(obj)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,104 @@
1
+ # rubocop:disable Naming/MethodParameterName
2
+
3
+ module Eco::Language::Delegation
4
+ module DelegatingMissing
5
+ class << self
6
+ def included(base)
7
+ super
8
+
9
+ base.extend Eco::Language::Klass::HelpersBuilt
10
+ base.extend ClassMethods
11
+ base.inheritable_class_vars :_delegating_missing, :delegating_missing_to
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def delegating_missing(*these, to:)
17
+ msg = "Delegating to: should be String or Symbol. Given: #{to.class}"
18
+ raise ArgumentError, msg unless sym_or_string?(to)
19
+
20
+ these.map do |this|
21
+ msg = "Expecting String or Symbol. Given: #{this}."
22
+ raise ArgumentError, msg unless sym_or_string?(this)
23
+
24
+ this.to_sym
25
+ end.each do |this|
26
+ _delegating_missing[this] = to
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def delegating_missing_to(to = nil)
33
+ return @delegating_missing_to if to.nil?
34
+
35
+ msg = "Expecting String or Symbol. Given: #{to}."
36
+ raise ArgumentError, msg unless sym_or_string?(to)
37
+
38
+
39
+ @delegating_missing_to = to.to_sym
40
+ end
41
+
42
+ def delegating_missing?(sym)
43
+ return true if sym.nil? && delegating_missing_to
44
+ return false unless sym_or_string?(sym)
45
+ return true if _delegating_missing.key?(sym.to_sym)
46
+ return true if delegating_missing_to
47
+
48
+ false
49
+ end
50
+
51
+ def delegating_missing_target(instance, at: nil)
52
+ return unless delegating_missing?(at)
53
+ return unless (subject_ref = _delegating_missing[at] || delegating_missing_to)
54
+
55
+ instance_has_method =
56
+ instance.methods.include?(subject_ref) ||
57
+ instance.private_methods.include?(subject_ref)
58
+
59
+ return instance.method(subject_ref).call if instance_has_method
60
+ return unless at.to_s.start_with?('@')
61
+ return unless instance.instance_variable_defined?(at)
62
+
63
+ instance.instance_variable_get(at)
64
+ end
65
+
66
+ private
67
+
68
+ def _delegating_missing
69
+ @_delegating_missing ||= {}
70
+ end
71
+
72
+ def sym_or_string?(value)
73
+ return false unless value.is_a?(Symbol) || value.is_a?(String)
74
+ return false if value.to_s.strip.empty?
75
+
76
+ true
77
+ end
78
+ end
79
+
80
+ # INSTANCE Methods
81
+
82
+ def respond_to_missing?(method_name, include_private = false)
83
+ super
84
+ end
85
+
86
+ private
87
+
88
+ # @note if missing, redirect all parameters to `receiver`,
89
+ # as long as responds_to `method_name`
90
+ def method_missing(method_name, *args, **kargs, &block)
91
+ return super unless self.class.delegating_missing?(method_name)
92
+
93
+ target = self.class.delegating_missing_target(self, at: method_name)
94
+
95
+ if method_name.to_s.start_with?('@')
96
+ target.instance_variable_get(method_name)
97
+ else
98
+ target.send(method_name, *args, **kargs, &block)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ # rubocop:enable Naming/MethodParameterName