convenient_service 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.gem_release.yml +0 -12
  3. data/.github/issue_template.md +35 -0
  4. data/.github/pull_request_template.md +31 -0
  5. data/.github/workflows/cd.yml +15 -2
  6. data/.github/workflows/ci.yml +6 -6
  7. data/CHANGELOG.md +27 -0
  8. data/README.md +12 -3
  9. data/ROADMAP.md +22 -8
  10. data/Taskfile.yml +5 -13
  11. data/env.rb +4 -0
  12. data/lib/convenient_service/common/plugins/caches_return_value/middleware.rb +17 -2
  13. data/lib/convenient_service/common/plugins/caches_return_value.rb +0 -1
  14. data/lib/convenient_service/common/plugins/has_constructor/concern.rb +13 -0
  15. data/lib/convenient_service/common/plugins/has_constructor_without_initialize/concern.rb +27 -0
  16. data/lib/convenient_service/common/plugins/has_constructor_without_initialize.rb +3 -0
  17. data/lib/convenient_service/common/plugins/has_internals/entities/internals/plugins/has_cache/concern.rb +8 -4
  18. data/lib/convenient_service/common/plugins/has_internals/entities/internals/plugins/has_cache.rb +0 -1
  19. data/lib/convenient_service/common/plugins.rb +1 -0
  20. data/lib/convenient_service/configs/standard.rb +16 -0
  21. data/lib/convenient_service/core/entities/config/entities/method_middlewares/entities/container/commands/cast_container.rb +0 -2
  22. data/lib/convenient_service/dependencies.rb +1 -0
  23. data/lib/convenient_service/examples/dry/gemfile/dry_service/config.rb +3 -1
  24. data/lib/convenient_service/examples/dry/gemfile/services/assert_file_exists.rb +1 -1
  25. data/lib/convenient_service/examples/dry/gemfile/services/assert_file_not_empty.rb +1 -1
  26. data/lib/convenient_service/examples/dry/gemfile/services/assert_npm_package_available.rb +1 -1
  27. data/lib/convenient_service/examples/dry/gemfile/services/parse_content.rb +1 -1
  28. data/lib/convenient_service/examples/dry/gemfile/services/print_shell_command.rb +1 -1
  29. data/lib/convenient_service/examples/dry/gemfile/services/read_file_content.rb +1 -1
  30. data/lib/convenient_service/examples/dry/gemfile/services/run_shell.rb +1 -1
  31. data/lib/convenient_service/examples/dry/gemfile/services/strip_comments.rb +1 -1
  32. data/lib/convenient_service/examples/dry/gemfile.rb +5 -4
  33. data/lib/convenient_service/examples/rails/gemfile/rails_service/config.rb +3 -1
  34. data/lib/convenient_service/examples/rails/gemfile.rb +5 -4
  35. data/lib/convenient_service/examples/standard/cowsay/services/build_cloud.rb +42 -0
  36. data/lib/convenient_service/examples/standard/cowsay/services/build_cow.rb +38 -0
  37. data/lib/convenient_service/examples/standard/cowsay/services/print.rb +32 -0
  38. data/lib/convenient_service/examples/standard/cowsay/services.rb +5 -0
  39. data/lib/convenient_service/examples/standard/cowsay.rb +24 -0
  40. data/lib/convenient_service/examples/standard/gemfile/services/format.rb +2 -0
  41. data/lib/convenient_service/examples/standard/gemfile.rb +5 -4
  42. data/lib/convenient_service/rspec/helpers/custom/stub_service/entities/result_spec.rb +96 -7
  43. data/lib/convenient_service/rspec/helpers/custom/wrap_method/entities/wrapped_method.rb +44 -8
  44. data/lib/convenient_service/rspec/helpers/custom/wrap_method/errors.rb +11 -9
  45. data/lib/convenient_service/rspec/matchers/custom/cache_its_value.rb +7 -7
  46. data/lib/convenient_service/rspec/matchers/custom/call_chain_next.rb +2 -0
  47. data/lib/convenient_service/rspec/matchers/custom/delegate_to.rb +50 -12
  48. data/lib/convenient_service/rspec/matchers/custom/results/be_success.rb +6 -0
  49. data/lib/convenient_service/service/plugins/aliases.rb +1 -0
  50. data/lib/convenient_service/service/plugins/has_inspect/concern.rb +23 -0
  51. data/lib/convenient_service/service/plugins/has_inspect.rb +3 -0
  52. data/lib/convenient_service/service/plugins/has_result/commands/create_result_class.rb +10 -0
  53. data/lib/convenient_service/service/plugins/has_result/concern/class_methods.rb +23 -14
  54. data/lib/convenient_service/service/plugins/has_result/concern/instance_methods.rb +17 -1
  55. data/lib/convenient_service/service/plugins/has_result/entities/result/concern.rb +4 -3
  56. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_inspect/concern.rb +31 -0
  57. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_inspect.rb +3 -0
  58. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/commands/cast_jsend_attributes.rb +37 -0
  59. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/commands.rb +3 -0
  60. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/concern/instance_methods.rb +127 -0
  61. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/concern.rb +27 -0
  62. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/code/class_methods.rb +34 -0
  63. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/code.rb +49 -0
  64. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/data/class_methods.rb +32 -0
  65. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/data.rb +49 -0
  66. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/message/class_methods.rb +34 -0
  67. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/message.rb +45 -0
  68. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/status/class_methods.rb +34 -0
  69. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/entities/status.rb +73 -0
  70. data/lib/convenient_service/service/plugins/has_result/entities/result/{entities.rb → plugins/has_jsend_status_and_attributes/entities.rb} +0 -0
  71. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/errors.rb +29 -0
  72. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/middleware.rb +31 -0
  73. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/structs/jsend_attributes.rb +21 -0
  74. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes/structs.rb +3 -0
  75. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins/has_jsend_status_and_attributes.rb +8 -0
  76. data/lib/convenient_service/service/plugins/has_result/entities/result/plugins.rb +2 -0
  77. data/lib/convenient_service/service/plugins/has_result/entities/result.rb +0 -4
  78. data/lib/convenient_service/service/plugins/has_result_method_steps/middleware.rb +1 -1
  79. data/lib/convenient_service/service/plugins/has_result_method_steps/services/method_step_config.rb +30 -4
  80. data/lib/convenient_service/service/plugins/has_result_params_validations/using_dry_validation/middleware.rb +19 -9
  81. data/lib/convenient_service/service/plugins/has_result_status_check_short_syntax/concern.rb +63 -0
  82. data/lib/convenient_service/service/plugins/has_result_status_check_short_syntax.rb +3 -0
  83. data/lib/convenient_service/service/plugins/has_result_steps/concern.rb +2 -1
  84. data/lib/convenient_service/service/plugins/has_result_steps/entities/step/plugins/has_inspect/concern.rb +31 -0
  85. data/lib/convenient_service/service/plugins/has_result_steps/entities/step/plugins/has_inspect.rb +3 -0
  86. data/lib/convenient_service/service/plugins/has_result_steps/entities/step/plugins.rb +3 -0
  87. data/lib/convenient_service/service/plugins/has_result_steps/entities/step.rb +1 -0
  88. data/lib/convenient_service/service/plugins/has_result_steps/middleware.rb +1 -1
  89. data/lib/convenient_service/service/plugins.rb +2 -0
  90. data/lib/convenient_service/support/cache/key.rb +100 -0
  91. data/lib/convenient_service/support/cache.rb +131 -0
  92. data/lib/convenient_service/support/copyable.rb +3 -2
  93. data/lib/convenient_service/support/delegate.rb +3 -2
  94. data/lib/convenient_service/support.rb +1 -0
  95. data/lib/convenient_service/version.rb +1 -1
  96. data/logo.png +0 -0
  97. metadata +40 -21
  98. data/lib/convenient_service/common/plugins/caches_return_value/entities/key.rb +0 -79
  99. data/lib/convenient_service/common/plugins/caches_return_value/entities.rb +0 -3
  100. data/lib/convenient_service/common/plugins/has_internals/entities/internals/plugins/has_cache/entities/cache.rb +0 -81
  101. data/lib/convenient_service/common/plugins/has_internals/entities/internals/plugins/has_cache/entities.rb +0 -3
  102. data/lib/convenient_service/service/plugins/has_result/entities/result/commands/cast_result_params.rb +0 -33
  103. data/lib/convenient_service/service/plugins/has_result/entities/result/commands.rb +0 -3
  104. data/lib/convenient_service/service/plugins/has_result/entities/result/concern/instance_methods.rb +0 -64
  105. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/code/class_methods.rb +0 -30
  106. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/code.rb +0 -45
  107. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/data/class_methods.rb +0 -28
  108. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/data.rb +0 -45
  109. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/message/class_methods.rb +0 -30
  110. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/message.rb +0 -41
  111. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/status/class_methods.rb +0 -30
  112. data/lib/convenient_service/service/plugins/has_result/entities/result/entities/status.rb +0 -69
  113. data/lib/convenient_service/service/plugins/has_result/entities/result/errors.rb +0 -25
  114. data/lib/convenient_service/service/plugins/has_result/entities/result/structs/result_params.rb +0 -17
  115. data/lib/convenient_service/service/plugins/has_result/entities/result/structs.rb +0 -3
@@ -4,17 +4,18 @@ require_relative "gemfile/rails_service"
4
4
  require_relative "gemfile/services"
5
5
 
6
6
  ##
7
- # Usage example:
7
+ # @internal
8
+ # Usage example:
8
9
  #
9
- # result = ConvenientService::Examples::Rails::Gemfile.format(path: "Gemfile")
10
- # result = ConvenientService::Examples::Rails::Gemfile.format(path: "spec/cli/gemfile/format/fixtures/Gemfile")
10
+ # result = ConvenientService::Examples::Rails::Gemfile.format("Gemfile")
11
+ # result = ConvenientService::Examples::Rails::Gemfile.format("spec/cli/gemfile/format/fixtures/Gemfile")
11
12
  #
12
13
  module ConvenientService
13
14
  module Examples
14
15
  module Rails
15
16
  module Gemfile
16
17
  class << self
17
- def format
18
+ def format(path)
18
19
  Services::Format[path: path]
19
20
  end
20
21
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Examples
5
+ module Standard
6
+ module Cowsay
7
+ module Services
8
+ class BuildCloud
9
+ include ConvenientService::Standard::Config
10
+
11
+ attr_reader :text
12
+
13
+ def initialize(text: "Hello World!")
14
+ @text = text
15
+ end
16
+
17
+ def result
18
+ success(cloud: cloud)
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # Copied with ❤️ from https://github.com/gaborbata/rosetta-cow
25
+ #
26
+ def cloud
27
+ <<~HEREDOC
28
+ #{border(text, "_")}
29
+ < #{text} >
30
+ #{border(text, "-")}
31
+ HEREDOC
32
+ end
33
+
34
+ def border(text, char)
35
+ char * (text.length + 2)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Examples
5
+ module Standard
6
+ module Cowsay
7
+ module Services
8
+ class BuildCow
9
+ include ConvenientService::Standard::Config
10
+
11
+ def result
12
+ success(cow: cow)
13
+ end
14
+
15
+ private
16
+
17
+ ##
18
+ # Copied with ❤️ from https://github.com/gaborbata/rosetta-cow
19
+ #
20
+ def cow
21
+ <<~'HEREDOC'.split("\n").map { |line| offset(line) }.join("\n")
22
+ \ ^__^
23
+ \ (oo)\_______
24
+ (__)\ )\/\
25
+ ||----w |
26
+ || ||
27
+ HEREDOC
28
+ end
29
+
30
+ def offset(line)
31
+ " " * 10 + line
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConvenientService
4
+ module Examples
5
+ module Standard
6
+ module Cowsay
7
+ module Services
8
+ class Print
9
+ include ConvenientService::Standard::Config
10
+
11
+ attr_reader :text, :out
12
+
13
+ step Services::BuildCloud, in: :text, out: :cloud
14
+ step Services::BuildCow, out: :cow
15
+ step :result
16
+
17
+ def initialize(text: "Hello World!", out: $stdout)
18
+ @text = text
19
+ @out = out
20
+ end
21
+
22
+ def result
23
+ out.puts cloud + cow
24
+
25
+ success
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "services/build_cloud"
4
+ require_relative "services/build_cow"
5
+ require_relative "services/print"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cowsay/services"
4
+
5
+ ##
6
+ # @internal
7
+ # Usage examples:
8
+ #
9
+ # result = ConvenientService::Examples::Standard::Cowsay.print("Hello")
10
+ # result = ConvenientService::Examples::Standard::Cowsay.print("Hi")
11
+ #
12
+ module ConvenientService
13
+ module Examples
14
+ module Standard
15
+ module Cowsay
16
+ class << self
17
+ def print(text = "Hello World!", out: $stdout)
18
+ Services::Print[text: text, out: out]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -14,6 +14,8 @@ module ConvenientService
14
14
  class Format
15
15
  include ConvenientService::Standard::Config
16
16
 
17
+ attr_reader :path
18
+
17
19
  step Services::ReadFileContent, in: :path, out: :content
18
20
  step Services::StripComments, in: :content, out: :content_without_comments
19
21
  step Services::ParseContent, in: {content: :content_without_comments}, out: :parsed_content
@@ -3,17 +3,18 @@
3
3
  require_relative "gemfile/services"
4
4
 
5
5
  ##
6
- # Usage example:
6
+ # @internal
7
+ # Usage example:
7
8
  #
8
- # result = ConvenientService::Examples::Standard::Gemfile.format(path: "Gemfile")
9
- # result = ConvenientService::Examples::Standard::Gemfile.format(path: "spec/cli/gemfile/format/fixtures/Gemfile")
9
+ # result = ConvenientService::Examples::Standard::Gemfile.format("Gemfile")
10
+ # result = ConvenientService::Examples::Standard::Gemfile.format("spec/cli/gemfile/format/fixtures/Gemfile")
10
11
  #
11
12
  module ConvenientService
12
13
  module Examples
13
14
  module Standard
14
15
  module Gemfile
15
16
  class << self
16
- def format
17
+ def format(path)
17
18
  Services::Format[path: path]
18
19
  end
19
20
  end
@@ -5,72 +5,140 @@ module ConvenientService
5
5
  module Helpers
6
6
  module Custom
7
7
  ##
8
- # TODO: Specs.
8
+ # @internal
9
+ # TODO: Specs.
9
10
  #
10
11
  class StubService < Support::Command
11
12
  module Entities
12
13
  class ResultSpec
14
+ ##
15
+ # @param status [Symbol]
16
+ # @param service_class [Class]
17
+ # @param chain [Hash]
18
+ # @return [void]
19
+ # @since 0.1.0
20
+ #
13
21
  def initialize(status:, service_class: nil, chain: {})
14
22
  @status = status
15
23
  @service_class = service_class
16
24
  @chain = chain
17
25
  end
18
26
 
27
+ ##
28
+ # @param service_class [Class]
29
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
30
+ # @since 0.1.0
31
+ #
19
32
  def for(service_class)
20
33
  self.class.new(status: status, service_class: service_class, chain: chain)
21
34
  end
22
35
 
36
+ ##
37
+ # @param data [Hash]
38
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
39
+ # @since 0.1.0
40
+ #
23
41
  def with_data(data)
24
42
  chain[:data] = data
25
43
 
26
44
  self
27
45
  end
28
46
 
47
+ ##
48
+ # @param message [String]
49
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
50
+ # @since 0.1.0
51
+ #
29
52
  def with_message(message)
30
53
  chain[:message] = message
31
54
 
32
55
  self
33
56
  end
34
57
 
58
+ ##
59
+ # @param code [String]
60
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
61
+ # @since 0.1.0
62
+ #
35
63
  def with_code(code)
36
64
  chain[:code] = code
37
65
 
38
66
  self
39
67
  end
40
68
 
69
+ ##
70
+ # @param data [Hash]
71
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
72
+ # @since 0.1.0
73
+ #
41
74
  def and_data(data)
42
75
  chain[:data] = data
43
76
 
44
77
  self
45
78
  end
46
79
 
80
+ ##
81
+ # @param message [String]
82
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
83
+ # @since 0.1.0
84
+ #
47
85
  def and_message(message)
48
86
  chain[:message] = message
49
87
 
50
88
  self
51
89
  end
52
90
 
91
+ ##
92
+ # @param code [String]
93
+ # @return [ConvenientService::RSpec::Helpers::Custom::StubService::Entities::ResultSpec]
94
+ # @since 0.1.0
95
+ #
53
96
  def and_code(code)
54
97
  chain[:code] = code
55
98
 
56
99
  self
57
100
  end
58
101
 
102
+ ##
103
+ # @return [Object]
104
+ #
105
+ # @internal
106
+ # TODO: Assert.
107
+ #
59
108
  def calculate_value
60
- ##
61
- # TODO: Assert.
62
- #
63
109
  service_class.__send__(status, **kwargs)
64
110
  end
65
111
 
66
112
  private
67
113
 
68
- attr_reader :status, :service_class, :chain
69
-
114
+ ##
115
+ # @!attribute [r] status
116
+ # @return [Symbol]
117
+ #
118
+ attr_reader :status
119
+
120
+ ##
121
+ # @!attribute [r] service_class
122
+ # @return [Class]
123
+ #
124
+ attr_reader :service_class
125
+
126
+ ##
127
+ # @!attribute [r] chain
128
+ # @return [Hash]
129
+ #
130
+ attr_reader :chain
131
+
132
+ ##
133
+ # @return [Hash]
134
+ #
70
135
  def kwargs
71
136
  @kwargs ||= calculate_kwargs
72
137
  end
73
138
 
139
+ ##
140
+ # @return [Hash]
141
+ #
74
142
  def calculate_kwargs
75
143
  kwargs = {}
76
144
 
@@ -83,32 +151,53 @@ module ConvenientService
83
151
  kwargs
84
152
  end
85
153
 
154
+ ##
155
+ # @return [Boolean]
156
+ #
86
157
  def used_data?
87
158
  chain.key?(:data)
88
159
  end
89
160
 
161
+ ##
162
+ # @return [Boolean]
163
+ #
90
164
  def used_message?
91
165
  chain.key?(:message)
92
166
  end
93
167
 
168
+ ##
169
+ # @return [Boolean]
170
+ #
94
171
  def used_code?
95
172
  chain.key?(:code)
96
173
  end
97
174
 
175
+ ##
176
+ # @return [Hash]
177
+ #
98
178
  def data
99
179
  @data ||= chain[:data] || {}
100
180
  end
101
181
 
182
+ ##
183
+ # @return [String]
184
+ #
102
185
  def message
103
186
  @message ||= chain[:message] || ""
104
187
  end
105
188
 
189
+ ##
190
+ # @return [String]
191
+ #
106
192
  def code
107
193
  @code ||= chain[:code] || ""
108
194
  end
109
195
 
196
+ ##
197
+ # @return [Object]
198
+ #
110
199
  def service_instance
111
- @service_instance ||= service_class.new_without_initialize
200
+ @service_instance ||= service_class.create_without_initialize
112
201
  end
113
202
  end
114
203
  end
@@ -4,17 +4,23 @@ module ConvenientService
4
4
  module RSpec
5
5
  module Helpers
6
6
  module Custom
7
- ##
8
- # TODO: Specs.
9
- #
10
7
  class WrapMethod < Support::Command
11
8
  module Entities
12
9
  ##
13
- # NOTE: Do NOT pollute the interface of this class until really needed. Avoid even pollution of private methods.
10
+ # @internal
11
+ # TODO: A better name.
12
+ #
13
+ # NOTE: Do NOT pollute the interface of this class until really needed. Avoid even pollution of private methods.
14
14
  #
15
15
  class WrappedMethod
16
16
  ##
17
- # NOTE: `middlewares` are `MethodChainMiddleware` classes.
17
+ # @param entity [Object, Class]
18
+ # @param method [String]
19
+ # @param middlewares [Array<ConvenientService::Core::MethodChainMiddleware>]
20
+ # @return [void]
21
+ #
22
+ # @internal
23
+ # NOTE: `middlewares` are `MethodChainMiddleware` classes.
18
24
  #
19
25
  def initialize(entity:, method:, middlewares:)
20
26
  @entity = entity
@@ -29,9 +35,6 @@ module ConvenientService
29
35
  #
30
36
  stack.use(
31
37
  proc do |env|
32
- ##
33
- # TODO: Enforce to always pass args, kwargs, block.
34
- #
35
38
  entity.__send__(method, *env[:args], **env[:kwargs], &env[:block])
36
39
  .tap { |value| @chain_value = value }
37
40
  .tap { @chain_arguments = {args: env[:args], kwargs: env[:kwargs], block: env[:block]} }
@@ -40,32 +43,65 @@ module ConvenientService
40
43
  end
41
44
  end
42
45
 
46
+ ##
47
+ # @param args [Array]
48
+ # @param kwargs [Hash]
49
+ # @param block [Proc]
50
+ # @return [Object] Can be any type.
51
+ #
43
52
  def call(*args, **kwargs, &block)
44
53
  @stack.call(entity: @entity, method: @method, args: args, kwargs: kwargs, block: block)
45
54
  end
46
55
 
56
+ ##
57
+ # @return [void]
58
+ #
59
+ def reset!
60
+ remove_instance_variable(:@chain_value) if defined? @chain_value
61
+ remove_instance_variable(:@chain_arguments) if defined? @chain_arguments
62
+ end
63
+
64
+ ##
65
+ # @return [Boolean]
66
+ #
47
67
  def chain_called?
48
68
  Utils::Bool.to_bool(defined? @chain_value)
49
69
  end
50
70
 
71
+ ##
72
+ # @return [Object] Can be any type.
73
+ # @raise [ConvenientService::RSpec::Helpers::Custom::WrapMethod::Errors::ChainAttributePreliminaryAccess]
74
+ #
51
75
  def chain_value
52
76
  raise Errors::ChainAttributePreliminaryAccess.new(attribute: :value) unless chain_called?
53
77
 
54
78
  @chain_value
55
79
  end
56
80
 
81
+ ##
82
+ # @return [Array]
83
+ # @raise [ConvenientService::RSpec::Helpers::Custom::WrapMethod::Errors::ChainAttributePreliminaryAccess]
84
+ #
57
85
  def chain_args
58
86
  raise Errors::ChainAttributePreliminaryAccess.new(attribute: :args) unless chain_called?
59
87
 
60
88
  @chain_arguments[:args]
61
89
  end
62
90
 
91
+ ##
92
+ # @return [Hash]
93
+ # @raise [ConvenientService::RSpec::Helpers::Custom::WrapMethod::Errors::ChainAttributePreliminaryAccess]
94
+ #
63
95
  def chain_kwargs
64
96
  raise Errors::ChainAttributePreliminaryAccess.new(attribute: :kwargs) unless chain_called?
65
97
 
66
98
  @chain_arguments[:kwargs]
67
99
  end
68
100
 
101
+ ##
102
+ # @return [Proc, nil]
103
+ # @raise [ConvenientService::RSpec::Helpers::Custom::WrapMethod::Errors::ChainAttributePreliminaryAccess]
104
+ #
69
105
  def chain_block
70
106
  raise Errors::ChainAttributePreliminaryAccess.new(attribute: :block) unless chain_called?
71
107
 
@@ -5,16 +5,18 @@ module ConvenientService
5
5
  module Helpers
6
6
  module Custom
7
7
  class WrapMethod < Support::Command
8
- class WrappedMethod
9
- module Errors
10
- class ChainAttributePreliminaryAccess < ::StandardError
11
- def initialize(attribute:)
12
- message <<~TEXT
13
- Chain attribute `#{attribute}` is accessed before the chain is called.
14
- TEXT
8
+ module Errors
9
+ class ChainAttributePreliminaryAccess < ConvenientService::Error
10
+ ##
11
+ # @param attribute [Symbol]
12
+ # @return [void]
13
+ #
14
+ def initialize(attribute:)
15
+ message = <<~TEXT
16
+ Chain attribute `#{attribute}` is accessed before the chain is called.
17
+ TEXT
15
18
 
16
- super(message)
17
- end
19
+ super(message)
18
20
  end
19
21
  end
20
22
  end
@@ -23,26 +23,26 @@ module ConvenientService
23
23
  end
24
24
 
25
25
  def failure_message
26
- "expected #{printable_block} to cache its value"
26
+ "expected #{printable_block_expectation} to cache its value"
27
27
  end
28
28
 
29
29
  def failure_message_when_negated
30
- "expected #{printable_block} NOT to cache its value"
30
+ "expected #{printable_block_expectation} NOT to cache its value"
31
31
  end
32
32
 
33
33
  ##
34
34
  # NOTE: An example of how RSpec extracts block source, but they marked it as private.
35
35
  # https://github.com/rspec/rspec-expectations/blob/311aaf245f2c5493572bf683b8c441cb5f7e44c8/lib/rspec/matchers/built_in/change.rb#L437
36
36
  #
37
- # TODO: `printable_block` when `method_source` is available.
37
+ # TODO: `printable_block_expectation` when `method_source` is available.
38
38
  # https://github.com/banister/method_source
39
39
  #
40
- # def printable_block
41
- # @printable_block ||= block_expectation.source
40
+ # def printable_block_expectation
41
+ # @printable_block_expectation ||= block_expectation.source
42
42
  # end
43
43
  #
44
- def printable_block
45
- @printable_block ||= "{ ... }"
44
+ def printable_block_expectation
45
+ @printable_block_expectation ||= "{ ... }"
46
46
  end
47
47
 
48
48
  private
@@ -84,6 +84,8 @@ module ConvenientService
84
84
  end
85
85
 
86
86
  def on(method)
87
+ method.reset!
88
+
87
89
  chain[:method] = method
88
90
 
89
91
  self
@@ -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