convenient_service 0.2.0 → 0.3.0

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