convenient_service 0.2.0 → 0.3.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -6
  3. data/CHANGELOG.md +16 -0
  4. data/ROADMAP.md +5 -0
  5. data/Taskfile.yml +3 -3
  6. data/env.rb +4 -0
  7. data/lib/convenient_service/configs/standard.rb +16 -0
  8. data/lib/convenient_service/dependencies/built_in.rb +8 -0
  9. data/lib/convenient_service/dependencies.rb +37 -0
  10. data/lib/convenient_service/examples/dry/gemfile/dry_service/config.rb +3 -1
  11. data/lib/convenient_service/examples/dry/gemfile/services/assert_file_exists.rb +1 -1
  12. data/lib/convenient_service/examples/dry/gemfile/services/assert_file_not_empty.rb +1 -1
  13. data/lib/convenient_service/examples/dry/gemfile/services/assert_npm_package_available.rb +1 -1
  14. data/lib/convenient_service/examples/dry/gemfile/services/parse_content.rb +1 -1
  15. data/lib/convenient_service/examples/dry/gemfile/services/print_shell_command.rb +1 -1
  16. data/lib/convenient_service/examples/dry/gemfile/services/read_file_content.rb +1 -1
  17. data/lib/convenient_service/examples/dry/gemfile/services/run_shell.rb +1 -1
  18. data/lib/convenient_service/examples/dry/gemfile/services/strip_comments.rb +1 -1
  19. data/lib/convenient_service/examples/rails/gemfile/rails_service/config.rb +3 -1
  20. data/lib/convenient_service/examples/rails/gemfile/services/assert_file_exists.rb +1 -1
  21. data/lib/convenient_service/examples/rails/gemfile/services/assert_file_not_empty.rb +1 -1
  22. data/lib/convenient_service/examples/rails/gemfile/services/assert_npm_package_available.rb +1 -1
  23. data/lib/convenient_service/examples/rails/gemfile/services/parse_content.rb +1 -1
  24. data/lib/convenient_service/examples/rails/gemfile/services/print_shell_command.rb +1 -1
  25. data/lib/convenient_service/examples/rails/gemfile/services/read_file_content.rb +1 -1
  26. data/lib/convenient_service/examples/rails/gemfile/services/run_shell.rb +1 -1
  27. data/lib/convenient_service/examples/rails/gemfile/services/strip_comments.rb +1 -1
  28. data/lib/convenient_service/rspec/helpers/custom/stub_service/entities/result_spec.rb +17 -1
  29. data/lib/convenient_service/rspec/helpers/custom/stub_service/entities/stubbed_service.rb +47 -53
  30. data/lib/convenient_service/rspec/matchers/custom/cache_its_value.rb +7 -7
  31. data/lib/convenient_service/rspec/matchers/custom/delegate_to.rb +50 -12
  32. data/lib/convenient_service/service/plugins/can_have_stubbed_result/concern.rb +32 -0
  33. data/lib/convenient_service/service/plugins/can_have_stubbed_result/middleware.rb +36 -0
  34. data/lib/convenient_service/service/plugins/can_have_stubbed_result.rb +4 -0
  35. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/concern/instance_methods.rb +29 -17
  36. data/lib/convenient_service/service/plugins/has_result_params_validations/using_dry_validation/middleware.rb +19 -9
  37. data/lib/convenient_service/service/plugins/has_result_steps/middleware.rb +1 -1
  38. data/lib/convenient_service/service/plugins.rb +1 -0
  39. data/lib/convenient_service/support/cache.rb +26 -0
  40. data/lib/convenient_service/support/gems/active_model.rb +36 -0
  41. data/lib/convenient_service/support/gems/rspec.rb +36 -0
  42. data/lib/convenient_service/support/gems.rb +4 -0
  43. data/lib/convenient_service/support/ruby.rb +22 -0
  44. data/lib/convenient_service/support/version/null_version.rb +78 -0
  45. data/lib/convenient_service/support/version.rb +65 -0
  46. data/lib/convenient_service/support.rb +3 -0
  47. data/lib/convenient_service/utils/object/instance_variable_fetch.rb +53 -0
  48. data/lib/convenient_service/utils/object.rb +9 -0
  49. data/lib/convenient_service/version.rb +1 -1
  50. metadata +12 -2
@@ -1,5 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # IMPORTANT: This matcher has a dedicated end-user doc. Do NOT forget to update it when needed.
5
+ # https://github.com/marian13/convenient_service_docs/blob/main/docs/api/tests/rspec/matchers/delegate_to.mdx
6
+ #
7
+ # TODO: Refactor into composition:
8
+ # - Ability to compose when `delegate_to` is used `without_arguments`.
9
+ # - Ability to compose when `delegate_to` is used `with_arguments`.
10
+ # - Ability to compose when `delegate_to` is used `and_return_its_value`.
11
+ #
12
+ # TODO: Refactor to NOT use `expect` inside this matcher.
13
+ # This way the matcher will return true or false, but never raise exceptions (descendant of Exception, not StandardError).
14
+ # Then it will be easier to developer a fully comprehensive spec suite for `delegate_to`.
15
+ #
3
16
  module ConvenientService
4
17
  module RSpec
5
18
  module Matchers
@@ -81,15 +94,7 @@ module ConvenientService
81
94
  # https://relishapp.com/rspec/rspec-mocks/docs/configuring-responses/wrapping-the-original-implementation
82
95
  #
83
96
  allow(object).to receive(method).and_wrap_original do |original, *actual_args, **actual_kwargs, &actual_block|
84
- ##
85
- # TODO: Provide customized error messages?
86
- # https://relishapp.com/rspec/rspec-expectations/docs/customized-message
87
- #
88
- # NOTE: `delegate_to` expects that delegation is executed only once during `block_expectation`.
89
- #
90
- expect(actual_args).to eq(expected_args)
91
- expect(actual_kwargs).to eq(expected_kwargs)
92
- expect(actual_block).to eq(expected_block)
97
+ actual_arguments_collection << [actual_args, actual_kwargs, actual_block]
93
98
 
94
99
  ##
95
100
  # NOTE: Imitates `and_call_original`.
@@ -108,7 +113,7 @@ module ConvenientService
108
113
  ##
109
114
  # NOTE: If this expectation fails, it means `delegate_to` is NOT met.
110
115
  #
111
- expect(object).to have_received(method)
116
+ expect(object).to have_received(method).at_least(1) unless used_with_arguments?
112
117
 
113
118
  ##
114
119
  # IMPORTANT: `and_return_its_value` works only when `delegate_to` checks a pure function.
@@ -166,7 +171,13 @@ module ConvenientService
166
171
  # NOTE: RSpec raises exception when any `expect` is NOT satisfied.
167
172
  # So, this `true` is returned only when all `expect` are successful.
168
173
  #
169
- true
174
+ if used_with_arguments?
175
+ actual_arguments_collection.any? do |(actual_args, actual_kwargs, actual_block)|
176
+ actual_args == expected_args && actual_kwargs == expected_kwargs && actual_block == expected_block
177
+ end
178
+ else
179
+ true
180
+ end
170
181
  end
171
182
 
172
183
  ##
@@ -184,8 +195,16 @@ module ConvenientService
184
195
  "delegate to `#{printable_method}`"
185
196
  end
186
197
 
198
+ def failure_message
199
+ if used_with_arguments?
200
+ "expected `#{printable_block_expectation}` to delegate to `#{printable_method}` with expected arguments at least once, but it didn't."
201
+ else
202
+ "expected `#{printable_block_expectation}` to delegate to `#{printable_method}` at least once, but it didn't."
203
+ end
204
+ end
205
+
187
206
  ##
188
- # IMPORTANT: `failure_message`, `failure_message_when_negated` are NOT implemented, since they are never called (since `matches?` always returns `true`).
207
+ # IMPORTANT: `failure_message_when_negated` is NOT supported yet.
189
208
  #
190
209
 
191
210
  def with_arguments(*args, **kwargs, &block)
@@ -248,6 +267,25 @@ module ConvenientService
248
267
  end
249
268
 
250
269
  alias_method :expected_block, :block
270
+
271
+ def actual_arguments_collection
272
+ @actual_arguments_collection ||= []
273
+ end
274
+
275
+ ##
276
+ # NOTE: An example of how RSpec extracts block source, but they marked it as private.
277
+ # https://github.com/rspec/rspec-expectations/blob/311aaf245f2c5493572bf683b8c441cb5f7e44c8/lib/rspec/matchers/built_in/change.rb#L437
278
+ #
279
+ # TODO: `printable_block_expectation` when `method_source` is available.
280
+ # https://github.com/banister/method_source
281
+ #
282
+ # def printable_block_expectation
283
+ # @printable_block_expectation ||= block_expectation.source
284
+ # end
285
+ #
286
+ def printable_block_expectation
287
+ @printable_block_expectation ||= "{ ... }"
288
+ end
251
289
  end
252
290
  end
253
291
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Service
5
+ module Plugins
6
+ module CanHaveStubbedResult
7
+ module Concern
8
+ include Support::Concern
9
+
10
+ class_methods do
11
+ ##
12
+ # @return [ConvenientService::Support::Cache]
13
+ #
14
+ # @internal
15
+ # `::RSpec.current_example` docs:
16
+ # - https://www.rubydoc.info/github/rspec/rspec-core/RSpec.current_example
17
+ # - https://github.com/rspec/rspec-core/blob/v3.12.0/lib/rspec/core.rb#L122
18
+ # - https://relishapp.com/rspec/rspec-core/docs/metadata/current-example
19
+ #
20
+ # TODO: Mutex for thread-safety when parallel steps will be supported.
21
+ #
22
+ def stubbed_results
23
+ cache = Utils::Object.instance_variable_fetch(::RSpec.current_example, :@__convenient_service_stubbed_results__) { Support::Cache.new }
24
+
25
+ cache.scope(self)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Service
5
+ module Plugins
6
+ module CanHaveStubbedResult
7
+ class Middleware < Core::MethodChainMiddleware
8
+ ##
9
+ # @param args [Array]
10
+ # @param kwargs [Hash]
11
+ # @param block [Proc]
12
+ # @return [Object] Can be any type.
13
+ #
14
+ def next(*args, **kwargs, &block)
15
+ key_with_arguments = cache.keygen(*args, **kwargs, &block)
16
+ key_without_arguments = cache.keygen
17
+
18
+ return cache[key_with_arguments] if cache.exist?(key_with_arguments)
19
+ return cache[key_without_arguments] if cache.exist?(key_without_arguments)
20
+
21
+ chain.next(*args, **kwargs, &block)
22
+ end
23
+
24
+ private
25
+
26
+ ##
27
+ # @return [ConvenientService::Support::Cache]
28
+ #
29
+ def cache
30
+ entity.stubbed_results
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "can_have_stubbed_result/concern"
4
+ require_relative "can_have_stubbed_result/middleware"
@@ -49,36 +49,48 @@ module ConvenientService
49
49
  ##
50
50
  # @return [Class]
51
51
  #
52
- def service
53
- internals.cache[:jsend_attributes].service
54
- end
52
+ delegate :service, to: :jsend_attributes
55
53
 
56
54
  ##
57
55
  # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Status]
58
56
  #
59
- def status
60
- internals.cache[:jsend_attributes].status
61
- end
57
+ delegate :status, to: :jsend_attributes
62
58
 
63
59
  ##
64
60
  # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Data]
65
61
  #
66
- def data
67
- internals.cache[:jsend_attributes].data
68
- end
62
+ delegate :data, to: :jsend_attributes
63
+
64
+ ##
65
+ # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Data]
66
+ #
67
+ alias_method :unsafe_data, :data
69
68
 
70
69
  ##
71
70
  # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Message]
72
71
  #
73
- def message
74
- internals.cache[:jsend_attributes].message
75
- end
72
+ delegate :message, to: :jsend_attributes
73
+
74
+ ##
75
+ # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Message]
76
+ #
77
+ alias_method :unsafe_message, :message
76
78
 
77
79
  ##
78
80
  # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Code]
79
81
  #
80
- def code
81
- internals.cache[:jsend_attributes].code
82
+ delegate :code, to: :jsend_attributes
83
+
84
+ ##
85
+ # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Entities::Code]
86
+ #
87
+ alias_method :unsafe_code, :code
88
+
89
+ ##
90
+ # @return [ConvenientService::Service::Plugins::HasResult::Entities::Result::Plugins::HasJsendStatusAndAttributes::Structs::JSendAttributes]
91
+ #
92
+ def jsend_attributes
93
+ internals.cache[:jsend_attributes]
82
94
  end
83
95
 
84
96
  ##
@@ -90,9 +102,9 @@ module ConvenientService
90
102
 
91
103
  return false if service.class != other.service.class
92
104
  return false if status != other.status
93
- return false if data != other.data
94
- return false if message != other.message
95
- return false if code != other.code
105
+ return false if unsafe_data != other.unsafe_data
106
+ return false if unsafe_message != other.unsafe_message
107
+ return false if unsafe_code != other.unsafe_code
96
108
 
97
109
  true
98
110
  end
@@ -21,15 +21,25 @@ module ConvenientService
21
21
  # TODO: Return one or all errors?
22
22
  #
23
23
  def errors
24
- @errors ||=
25
- entity
26
- .class
27
- .contract
28
- .new
29
- .call(**entity.constructor_params.kwargs)
30
- .errors
31
- .to_h
32
- .transform_values(&:first)
24
+ @errors ||= resolve_errors
25
+ end
26
+
27
+ def constructor_kwargs
28
+ @constructor_kwargs ||= entity.constructor_params.kwargs
29
+ end
30
+
31
+ def contract
32
+ @contract ||= entity.class.contract
33
+ end
34
+
35
+ def resolve_errors
36
+ return {} unless contract.schema
37
+
38
+ contract.new
39
+ .call(constructor_kwargs)
40
+ .errors
41
+ .to_h
42
+ .transform_values(&:first)
33
43
  end
34
44
  end
35
45
  end
@@ -8,7 +8,7 @@ module ConvenientService
8
8
  def next(...)
9
9
  return chain.next(...) if entity.steps.none?
10
10
 
11
- last_step.result
11
+ last_step.result.copy
12
12
  end
13
13
 
14
14
  private
@@ -4,6 +4,7 @@
4
4
  # NOTE: Order matters.
5
5
  #
6
6
  require_relative "plugins/can_recalculate_result"
7
+ require_relative "plugins/can_have_stubbed_result"
7
8
  require_relative "plugins/has_inspect"
8
9
  require_relative "plugins/has_result"
9
10
  require_relative "plugins/has_result_method_steps"
@@ -21,6 +21,16 @@ module ConvenientService
21
21
  end
22
22
  end
23
23
 
24
+ ##
25
+ # @return [Boolean]
26
+ #
27
+ # @internal
28
+ # https://ruby-doc.org/core-2.7.0/Hash.html#method-i-empty-3F
29
+ #
30
+ def empty?
31
+ hash.empty?
32
+ end
33
+
24
34
  ##
25
35
  # @return [Boolean]
26
36
  #
@@ -100,6 +110,22 @@ module ConvenientService
100
110
  exist?(key) ? read(key) : write(key, block.call)
101
111
  end
102
112
 
113
+ ##
114
+ # @return [ConvenientService::Support::Cache]
115
+ #
116
+ def clear
117
+ hash.clear
118
+
119
+ self
120
+ end
121
+
122
+ ##
123
+ # @return [ConvenientService::Support::Cache]
124
+ #
125
+ def scope(key)
126
+ fetch(key) { Support::Cache.new }
127
+ end
128
+
103
129
  ##
104
130
  # @return [ConvenientService::Support::Cache::Key]
105
131
  #
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Support
5
+ module Gems
6
+ ##
7
+ # @api private
8
+ #
9
+ class ActiveModel
10
+ class << self
11
+ ##
12
+ # @return [Boolean]
13
+ #
14
+ # @internal
15
+ # `Style/TernaryParentheses` is disabled since `defined?` has too low priority without parentheses.
16
+ #
17
+ # rubocop:disable Style/TernaryParentheses
18
+ def loaded?
19
+ (defined? ::ActiveModel) ? true : false
20
+ end
21
+ # rubocop:enable Style/TernaryParentheses
22
+
23
+ ##
24
+ # @return [ConvenientService::Support::Version]
25
+ #
26
+ # @internal
27
+ # https://github.com/rails/rails/blob/main/activemodel/lib/active_model/version.rb
28
+ #
29
+ def version
30
+ loaded? ? Support::Version.new(::ActiveModel.version) : Support::Version.null_version
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Support
5
+ module Gems
6
+ ##
7
+ # @api private
8
+ #
9
+ class RSpec
10
+ class << self
11
+ ##
12
+ # @return [Boolean]
13
+ #
14
+ # @internal
15
+ # `Style/TernaryParentheses` is disabled since `defined?` has too low priority without parentheses.
16
+ #
17
+ # rubocop:disable Style/TernaryParentheses
18
+ def loaded?
19
+ (defined? ::RSpec) ? true : false
20
+ end
21
+ # rubocop:enable Style/TernaryParentheses
22
+
23
+ ##
24
+ # @return [ConvenientService::Support::Version]
25
+ #
26
+ # @internal
27
+ # https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/version.rb
28
+ #
29
+ def version
30
+ loaded? ? Support::Version.new(::RSpec::Core::Version::STRING) : Support::Version.null_version
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gems/active_model"
4
+ require_relative "gems/rspec"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Support
5
+ ##
6
+ # @api private
7
+ #
8
+ class Ruby
9
+ class << self
10
+ ##
11
+ # @return [ConvenientService::Support::Version]
12
+ #
13
+ # @internal
14
+ # https://ruby-doc.org/core-2.7.2/doc/globals_rdoc.html
15
+ #
16
+ def version
17
+ @version ||= Support::Version.new(::RUBY_VERSION)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Support
5
+ class Version
6
+ ##
7
+ # @api private
8
+ #
9
+ # @internal
10
+ # - https://thoughtbot.com/blog/rails-refactoring-example-introduce-null-object
11
+ # - https://avdi.codes/null-objects-and-falsiness/
12
+ #
13
+ class NullVersion
14
+ ##
15
+ # @return [nil]
16
+ #
17
+ def gem_version
18
+ nil
19
+ end
20
+
21
+ ##
22
+ # @param other [Object] Can be any type.
23
+ # @return [nil]
24
+ #
25
+ def <=>(other)
26
+ nil
27
+ end
28
+
29
+ ##
30
+ # @param other [Object] Can be any type.
31
+ # @return [nil]
32
+ #
33
+ def <(other)
34
+ nil
35
+ end
36
+
37
+ ##
38
+ # @param other [Object] Can be any type.
39
+ # @return [nil]
40
+ #
41
+ def <=(other)
42
+ nil
43
+ end
44
+
45
+ ##
46
+ # @param other [Object] Can be any type.
47
+ # @return [nil]
48
+ #
49
+ def ==(other)
50
+ nil
51
+ end
52
+
53
+ ##
54
+ # @param other [Object] Can be any type.
55
+ # @return [nil]
56
+ #
57
+ def >(other)
58
+ nil
59
+ end
60
+
61
+ ##
62
+ # @param other [Object] Can be any type.
63
+ # @return [nil]
64
+ #
65
+ def >=(other)
66
+ nil
67
+ end
68
+
69
+ ##
70
+ # @return [String]
71
+ #
72
+ def to_s
73
+ ""
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "version/null_version"
4
+
5
+ module ConvenientService
6
+ module Support
7
+ class Version
8
+ include ::Comparable
9
+
10
+ undef_method :between?
11
+
12
+ undef_method :clamp
13
+
14
+ ##
15
+ # @param value [String]
16
+ # @return [void]
17
+ #
18
+ def initialize(value)
19
+ @value = value
20
+ end
21
+
22
+ class << self
23
+ ##
24
+ # @return [ConvenientService::Support::Version::NullVersion]
25
+ #
26
+ def null_version
27
+ @null_version ||= NullVersion.new
28
+ end
29
+ end
30
+
31
+ ##
32
+ # @return [Gem::Version, nil]
33
+ #
34
+ def gem_version
35
+ cast_gem_version(value)
36
+ end
37
+
38
+ ##
39
+ # @param other [Object] Can be any type.
40
+ # @return [Boolean, nil]
41
+ #
42
+ def <=>(other)
43
+ gem_version <=> cast_gem_version(other)
44
+ end
45
+
46
+ ##
47
+ # @return [String]
48
+ #
49
+ def to_s
50
+ gem_version.to_s
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :value
56
+
57
+ ##
58
+ # @return [Gem::Version, nil]
59
+ #
60
+ def cast_gem_version(value)
61
+ ::Gem::Version.create(value.to_s) if ::Gem::Version.correct?(value.to_s)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -9,4 +9,7 @@ require_relative "support/command"
9
9
  require_relative "support/copyable"
10
10
  require_relative "support/delegate"
11
11
  require_relative "support/finite_loop"
12
+ require_relative "support/gems"
12
13
  require_relative "support/middleware"
14
+ require_relative "support/ruby"
15
+ require_relative "support/version"
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ #
5
+ #
6
+ module ConvenientService
7
+ module Utils
8
+ module Object
9
+ class InstanceVariableFetch < Support::Command
10
+ ##
11
+ # @!attribute [r] object
12
+ # @return [Object]
13
+ #
14
+ attr_reader :object
15
+
16
+ ##
17
+ # @!attribute [r] ivar_name
18
+ # @return [Symbol, String]
19
+ #
20
+ attr_reader :ivar_name
21
+
22
+ ##
23
+ # @!attribute [r] fallback_block
24
+ # @return [Proc, nil]
25
+ #
26
+ attr_reader :fallback_block
27
+
28
+ ##
29
+ # @param object [Object]
30
+ # @param ivar_name [Symbol]
31
+ # @param fallback_block [Proc]
32
+ # @return [void]
33
+ #
34
+ def initialize(object, ivar_name, &fallback_block)
35
+ @object = object
36
+ @ivar_name = ivar_name
37
+ @fallback_block = fallback_block
38
+ end
39
+
40
+ ##
41
+ # @return [Object] Value of ivar. Can be any type.
42
+ #
43
+ def call
44
+ return object.instance_variable_get(ivar_name) if object.instance_variable_defined?(ivar_name)
45
+
46
+ return object.instance_variable_set(ivar_name, fallback_block.call) if fallback_block
47
+
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,11 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "object/instance_variable_fetch"
3
4
  require_relative "object/resolve_type"
4
5
 
5
6
  module ConvenientService
6
7
  module Utils
7
8
  module Object
8
9
  class << self
10
+ ##
11
+ # @example
12
+ # ConvenientService::Utils::Object.instance_variable_fetch("abc", :@foo) { :bar }
13
+ #
14
+ def instance_variable_fetch(...)
15
+ InstanceVariableFetch.call(...)
16
+ end
17
+
9
18
  ##
10
19
  # @example
11
20
  # ConvenientService::Utils::Object.resolve_type("foo")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConvenientService
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end