crossbeam 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +300 -0
- data/defs.rbi +294 -0
- data/lib/crossbeam/callbacks.rb +99 -0
- data/lib/crossbeam/error.rb +33 -0
- data/lib/crossbeam/errors.rb +76 -0
- data/lib/crossbeam/output.rb +98 -0
- data/lib/crossbeam/result.rb +68 -0
- data/lib/crossbeam/version.rb +6 -0
- data/lib/crossbeam.rb +108 -0
- data/lib/generators/crossbeam_generator.rb +36 -0
- data/lib/generators/templates/service_class.rb.tt +12 -0
- data/lib/generators/templates/service_spec.rb.tt +11 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f3e5a2f8df4b98114b1f734e78e22332078ea4f9f933fea9fe745f4113e1dab9
|
4
|
+
data.tar.gz: aae8768c3bd775c9e0939ae73b738b0ae2b00f50e2c1719116870f15e7fb3da4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c9c6b21bcf8058137a42317c0e18b24ec6cb9aaeb06ad34dacd318c38931c29bed9d657e66390f41e2df3462dbb5fd955c5438d3c6f10603bb94429099b0888
|
7
|
+
data.tar.gz: 80d02b81f62a3d9670802576cb27794050925ef15335b9b2472816813d5ba83286147855596c5ad73a388c7a9c4193c7ba894d5e873353a5a03f6e2988e2f6c5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.1.0] - 2022-06-18
|
9
|
+
|
10
|
+
- Initial release
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Brandon Hicks
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,300 @@
|
|
1
|
+
# Crossbeam
|
2
|
+
|
3
|
+
Crossbeam is a gem to making it easy to create and run ruby service objects. It allows you to use validations, errors, etc to take away the troubles associated with service classes.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
8
|
+
|
9
|
+
```shell
|
10
|
+
bundle add crossbeam
|
11
|
+
```
|
12
|
+
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
gem install crossbeam
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
In order to too use the Crossbeam service class you will need to add `include Crossbeam` to the class you wish to make a service object.
|
22
|
+
|
23
|
+
### Initializers
|
24
|
+
|
25
|
+
You can call and initialize a Crossbeam service call like any other ruby object and
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ServiceClass
|
29
|
+
include Crossbeam
|
30
|
+
|
31
|
+
def initialize(name, age, other: nil)
|
32
|
+
@age = age
|
33
|
+
@name = name
|
34
|
+
@other = other
|
35
|
+
end
|
36
|
+
|
37
|
+
def call
|
38
|
+
do_something
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def do_something
|
44
|
+
# .....
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Calling the service class
|
49
|
+
ServiceClass.call('James', 12)
|
50
|
+
```
|
51
|
+
|
52
|
+
Crossbeam also includes [dry-initializer], which allows you to quickly initialize object parameters.
|
53
|
+
This allows you to bypass having to setup an initialize method in order to assign all attributes to instance variables.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class OtherService
|
57
|
+
include Crossbeam
|
58
|
+
|
59
|
+
param :name, proc(&:to_s)
|
60
|
+
param :age, proc(&:to_i)
|
61
|
+
option :other, default: proc { nil }
|
62
|
+
|
63
|
+
def call
|
64
|
+
do_something
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def do_something
|
70
|
+
# .....
|
71
|
+
return "#{@name} is a minor" if @age < 18
|
72
|
+
|
73
|
+
"#{@name} is #{@age}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Calling the service class
|
78
|
+
OtherService.call('James', 12)
|
79
|
+
```
|
80
|
+
|
81
|
+
### Output
|
82
|
+
|
83
|
+
If you want skip assigning the last attribute returned from call to results you can specify a specific attribute to result reference after `#call` has been ran. This can be done by assigning an attribute to be assigned as the results with `output :attribute_name`.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class OutputSample
|
87
|
+
include Crossbeam
|
88
|
+
|
89
|
+
param :name, proc(&:to_s)
|
90
|
+
param :age, default: proc { 10 }
|
91
|
+
|
92
|
+
# Attribute/Instance Variable to return
|
93
|
+
output :age
|
94
|
+
|
95
|
+
def call
|
96
|
+
@age += 1
|
97
|
+
"Hello, #{name}! You are #{age} years old."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
output = OutputSample.call('James', 12)
|
102
|
+
output.results
|
103
|
+
```
|
104
|
+
|
105
|
+
### Callbacks
|
106
|
+
|
107
|
+
Similar to Rails actions or models Crossbeam allows you to have before/after callbacks for before `call` is ran. They are completely optional and either one can be used without the other. They before/after references can either by a symbol or a block.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class SampleClass
|
111
|
+
include Crossbeam
|
112
|
+
|
113
|
+
# Callbacks that will be called before/after #call is referenced
|
114
|
+
before do
|
115
|
+
# setup for #call
|
116
|
+
end
|
117
|
+
|
118
|
+
after :cleanup_script
|
119
|
+
|
120
|
+
def call
|
121
|
+
# .....
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def cleanup_script
|
127
|
+
# .....
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
SampleClass.call
|
132
|
+
```
|
133
|
+
|
134
|
+
### Errors and Validations
|
135
|
+
|
136
|
+
#### Errors
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class ErrorClass
|
140
|
+
include Crossbeam
|
141
|
+
|
142
|
+
def initialize(name, age)
|
143
|
+
@name = name
|
144
|
+
@age = age
|
145
|
+
end
|
146
|
+
|
147
|
+
def call
|
148
|
+
errors.add(:age, "#{@name} is a minor") if @age < 18
|
149
|
+
errors.add(:base, 'something something something')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
test = ErrorClass.call('James', 10)
|
154
|
+
test.errors
|
155
|
+
# => {:age=>["James is a minor"], :base=>["something something something"]}
|
156
|
+
test.errors.full_messages
|
157
|
+
# => ["Age James is a minor", "something something something"]
|
158
|
+
test.errors.to_s
|
159
|
+
# => Age James is a minor
|
160
|
+
# => something something something
|
161
|
+
```
|
162
|
+
|
163
|
+
#### Validations
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
require_relative 'crossbeam'
|
167
|
+
|
168
|
+
class AgeCheck
|
169
|
+
include Crossbeam
|
170
|
+
|
171
|
+
option :age, default: proc { 0 }
|
172
|
+
|
173
|
+
validates :age, numericality: { greater_than_or_equal_to: 18, less_than_or_equal_to: 65 }
|
174
|
+
|
175
|
+
def call
|
176
|
+
return 'Minor' unless valid?
|
177
|
+
|
178
|
+
'Adult'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
puts AgeCheck.call(age: 15).errors.full_messages
|
183
|
+
# => ["Age must be greater than or equal to 18"]
|
184
|
+
puts AgeCheck.call(age: 20).results
|
185
|
+
# => Adult
|
186
|
+
```
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
require_relative 'crossbeam'
|
190
|
+
|
191
|
+
class Bar
|
192
|
+
include Crossbeam
|
193
|
+
|
194
|
+
option :age, default: proc { 0 }
|
195
|
+
option :drink
|
196
|
+
option :description, default: proc { '' }
|
197
|
+
|
198
|
+
validates :age, numericality: { greater_than_or_equal_to: 21, less_than_or_equal_to: 65 }
|
199
|
+
validates :drink, inclusion: { in: %w(beer wine whiskey) }
|
200
|
+
validates :description, length: { minimum: 7, message: 'is required' }
|
201
|
+
|
202
|
+
def call
|
203
|
+
return 'Minor' unless valid?
|
204
|
+
|
205
|
+
'Adult'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
after_hours = Bar.call(age: 15, drink: 'tran')
|
210
|
+
puts after_hours.errors.full_messages if after_hours.errors?
|
211
|
+
# => Age must be greater than or equal to 21
|
212
|
+
# => Drink is not included in the list
|
213
|
+
# => Description is required
|
214
|
+
```
|
215
|
+
|
216
|
+
### Fail
|
217
|
+
|
218
|
+
If a particular condition is come across you may want to cause a service call to fail. This causes any further action within the service call to not be called and the classes result to be set as nil.
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class Something
|
222
|
+
include Crossbeam
|
223
|
+
|
224
|
+
def call
|
225
|
+
fail!('1 is less than 2') unless 1 > 2
|
226
|
+
|
227
|
+
true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
test = Something.call
|
232
|
+
test.failure? # => true
|
233
|
+
puts test.errors.full_messages # => ['1 is less than 2']
|
234
|
+
test.result # => nil
|
235
|
+
```
|
236
|
+
|
237
|
+
When calling `fail!` you need to supply a message/context very similar to an exception description. And when the service call is forced to fail no results should be returned.
|
238
|
+
|
239
|
+
### Generators (Rails)
|
240
|
+
|
241
|
+
The Crossbeam service class generator is only available when used with a rails application.
|
242
|
+
|
243
|
+
When running the generator you will specify the class name for the service object.
|
244
|
+
|
245
|
+
```shell
|
246
|
+
rails g crossbeam AgeCheck
|
247
|
+
```
|
248
|
+
|
249
|
+
Running this will generate a file `app/services/age_check.rb` with the following contents
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
# frozen_string_literal: true
|
253
|
+
|
254
|
+
class AgeCheck
|
255
|
+
include Crossbeam
|
256
|
+
|
257
|
+
def call
|
258
|
+
# ...
|
259
|
+
end
|
260
|
+
end
|
261
|
+
```
|
262
|
+
|
263
|
+
You can also specify attributes that you want use with the class.
|
264
|
+
|
265
|
+
`rails g crossbeam IdentityCheck address age dob name`
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
class IdentityCheck
|
269
|
+
include Crossbeam
|
270
|
+
|
271
|
+
option :address
|
272
|
+
option :age
|
273
|
+
option :dob
|
274
|
+
option :name
|
275
|
+
|
276
|
+
def call
|
277
|
+
# ...
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
## Contributing
|
283
|
+
|
284
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tarellel/crossbeam.
|
285
|
+
|
286
|
+
## License
|
287
|
+
|
288
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
289
|
+
|
290
|
+
This project is intended to be a safe, welcoming space for collaboration, and everyone interacting in the project’s codebase and issue tracker is expected to adhere to the [Contributor Covenant code of conduct](https://github.com/tarellel/crossbeam/main/CODE_OF_CONDUCT.md).
|
291
|
+
|
292
|
+
## Inspired by
|
293
|
+
|
294
|
+
* [Actor](https://github.com/sunny/actor)
|
295
|
+
* [Callee](https://github.com/dreikanter/callee)
|
296
|
+
* [CivilService](https://github.com/actblue/civil_service)
|
297
|
+
* [SimpleCommand](https://github.com/nebulab/simple_command)
|
298
|
+
* [u-case](https://github.com/serradura/u-case)
|
299
|
+
|
300
|
+
[dry-initializer]: <https://github.com/dry-rb/dry-initializer>
|
data/defs.rbi
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# Crossbeam module to include with your service classes
|
3
|
+
module Crossbeam
|
4
|
+
VERSION = T.let('0.1.0', T.untyped)
|
5
|
+
|
6
|
+
# Used to include/extend modules into the current class
|
7
|
+
#
|
8
|
+
# _@param_ `base`
|
9
|
+
sig { params(base: Object).void }
|
10
|
+
def self.included(base); end
|
11
|
+
|
12
|
+
# Force the job to raise an exception and stop the rest of the service call
|
13
|
+
#
|
14
|
+
# _@param_ `error`
|
15
|
+
sig { params(error: String).void }
|
16
|
+
def fail!(error); end
|
17
|
+
|
18
|
+
# Used to return a list of errors for the current call
|
19
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
20
|
+
def errors; end
|
21
|
+
|
22
|
+
# Methods to load in the service object as class methods
|
23
|
+
module ClassMethods
|
24
|
+
# Used to initiate and service call
|
25
|
+
#
|
26
|
+
# _@param_ `params`
|
27
|
+
#
|
28
|
+
# _@param_ `options`
|
29
|
+
sig { params(params: T::Array[Object], options: T::Hash[Symbol, Object], block: T.untyped).returns(T.any(Struct, Object)) }
|
30
|
+
def call(*params, **options, &block); end
|
31
|
+
|
32
|
+
# Call the klass's before/after callbacks, process errors, and call @klass
|
33
|
+
sig { params(block: T.untyped).void }
|
34
|
+
def run_callbacks_and_klass(&block); end
|
35
|
+
|
36
|
+
# Process the error that was thrown by the object call
|
37
|
+
#
|
38
|
+
# _@param_ `error` — error generated by the object call
|
39
|
+
sig { params(error: Object).returns(T.any(Object, Crossbeam::Result)) }
|
40
|
+
def process_error(error); end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Error message that is raised to flag a Crossbeam service error
|
44
|
+
# used to create error messages for Crossbeam
|
45
|
+
# @private
|
46
|
+
class Error < StandardError
|
47
|
+
# Used to initializee an error message
|
48
|
+
#
|
49
|
+
# _@param_ `context`
|
50
|
+
sig { params(context: T.nilable(T.any(String, T::Hash[T.untyped, T.untyped]))).void }
|
51
|
+
def initialize(context = nil); end
|
52
|
+
|
53
|
+
# _@return_ — Error message associated with StandardError
|
54
|
+
sig { returns(String) }
|
55
|
+
attr_reader :context
|
56
|
+
end
|
57
|
+
|
58
|
+
# Used to generate `ArguementError` exception
|
59
|
+
# @private
|
60
|
+
class ArguementError < Crossbeam::Error
|
61
|
+
end
|
62
|
+
|
63
|
+
# Used to generate `Failure` exception
|
64
|
+
# @private
|
65
|
+
class Failure < Crossbeam::Error
|
66
|
+
end
|
67
|
+
|
68
|
+
# Used to generate `NotImplementedError` exception
|
69
|
+
# @private
|
70
|
+
class NotImplementedError < Crossbeam::Error
|
71
|
+
end
|
72
|
+
|
73
|
+
# Used to generate `UndefinedMethod` exception
|
74
|
+
# @private
|
75
|
+
class UndefinedMethod < Crossbeam::Error
|
76
|
+
end
|
77
|
+
|
78
|
+
# Used to allow adding errors to the service call similar to ActiveRecord errors
|
79
|
+
class Errors < Hash
|
80
|
+
# Add an error to the list of errors
|
81
|
+
#
|
82
|
+
# _@param_ `key` — the key/attribute for the error. (Usually ends up being :base)
|
83
|
+
#
|
84
|
+
# _@param_ `value` — a description of the error
|
85
|
+
#
|
86
|
+
# _@param_ `_opts` — additional attributes that get ignored
|
87
|
+
sig { params(key: T.any(String, Symbol), value: T.any(String, Symbol), _opts: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped]) }
|
88
|
+
def add(key, value, _opts = {}); end
|
89
|
+
|
90
|
+
# Add multiple errors to the error hash
|
91
|
+
#
|
92
|
+
# _@param_ `errors`
|
93
|
+
#
|
94
|
+
# _@return_ — , Array]
|
95
|
+
sig { params(errors: T::Hash[String, Symbol]).returns(T::Hash[T.untyped, T.untyped]) }
|
96
|
+
def add_multiple_errors(errors); end
|
97
|
+
|
98
|
+
# Look through and return a list of all errorr messages
|
99
|
+
sig { returns(T.any(T::Hash[T.untyped, T.untyped], T::Array[T.untyped])) }
|
100
|
+
def each; end
|
101
|
+
|
102
|
+
# Return a full list of error messages
|
103
|
+
sig { returns(T::Array[String]) }
|
104
|
+
def full_messages; end
|
105
|
+
|
106
|
+
# Used to convert the list of errors to a string
|
107
|
+
sig { returns(String) }
|
108
|
+
def to_s; end
|
109
|
+
|
110
|
+
# Convert the message to a full error message
|
111
|
+
#
|
112
|
+
# _@param_ `attribute`
|
113
|
+
#
|
114
|
+
# _@param_ `message`
|
115
|
+
sig { params(attribute: T.any(String, Symbol), message: String).returns(String) }
|
116
|
+
def full_message(attribute, message); end
|
117
|
+
end
|
118
|
+
|
119
|
+
# For forcing specific output after `#call`
|
120
|
+
module Output
|
121
|
+
# Used to include/extend modules into the current class
|
122
|
+
#
|
123
|
+
# _@param_ `base`
|
124
|
+
sig { params(base: Object).void }
|
125
|
+
def self.included(base); end
|
126
|
+
|
127
|
+
# Methods to load in the service object as class methods
|
128
|
+
module ClassMethods
|
129
|
+
CB_ALLOWED_OUTPUTS = T.let([NilClass, String, Symbol].freeze, T.untyped)
|
130
|
+
|
131
|
+
# Used to specify an attribute/instance variable that should be used too return instead of @result
|
132
|
+
#
|
133
|
+
# _@param_ `param`
|
134
|
+
sig { params(param: T.any(String, Symbol)).returns(T.any(String, Symbol)) }
|
135
|
+
def output(param); end
|
136
|
+
|
137
|
+
# Determine if the output attribute type is allowed
|
138
|
+
#
|
139
|
+
# _@param_ `output_type`
|
140
|
+
sig { params(output_type: String).returns(T::Boolean) }
|
141
|
+
def allowed_output?(output_type); end
|
142
|
+
|
143
|
+
# Used to determine if a output parameter has been set or not
|
144
|
+
sig { returns(T::Boolean) }
|
145
|
+
def output?; end
|
146
|
+
|
147
|
+
# Determine the output to return if the instance_variable exists in the klass
|
148
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
149
|
+
def set_results_output; end
|
150
|
+
|
151
|
+
# Add errors to @result.errors
|
152
|
+
sig { void }
|
153
|
+
def build_error_list; end
|
154
|
+
|
155
|
+
# Reassign result, unless errors
|
156
|
+
sig { void }
|
157
|
+
def reassign_results; end
|
158
|
+
|
159
|
+
# Does the klass have an assigned output
|
160
|
+
sig { returns(T::Boolean) }
|
161
|
+
def specified_output?; end
|
162
|
+
|
163
|
+
# Used hold the parameter which can/will be used instead of @result
|
164
|
+
sig { returns(T.nilable(T.any(String, Symbol))) }
|
165
|
+
def output_param; end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Used as a data container to hold a service calls results, errors, etc.
|
170
|
+
# class Result < Struct.new(:called, :errors, :failure, :results)
|
171
|
+
class Result < Struct
|
172
|
+
# Used to initialize a service calls results, errors, and stats
|
173
|
+
#
|
174
|
+
# _@param_ `called`
|
175
|
+
#
|
176
|
+
# _@param_ `errors`
|
177
|
+
#
|
178
|
+
# _@param_ `failure`
|
179
|
+
#
|
180
|
+
# _@param_ `results`
|
181
|
+
sig do
|
182
|
+
params(
|
183
|
+
called: T::Boolean,
|
184
|
+
errors: T.nilable(T::Hash[T.untyped, T.untyped]),
|
185
|
+
failure: T::Boolean,
|
186
|
+
results: T.nilable(Object)
|
187
|
+
).void
|
188
|
+
end
|
189
|
+
def initialize(called: false, errors: nil, failure: false, results: nil); end
|
190
|
+
|
191
|
+
# The serivce can't officially be a failure/pass if it hasn't been "called"
|
192
|
+
sig { returns(T::Boolean) }
|
193
|
+
def called?; end
|
194
|
+
|
195
|
+
# Determine if the service call has any errors
|
196
|
+
sig { returns(T::Boolean) }
|
197
|
+
def errors?; end
|
198
|
+
|
199
|
+
# Determine if the service call has failed to uccessfully complete
|
200
|
+
sig { returns(T::Boolean) }
|
201
|
+
def failure?; end
|
202
|
+
|
203
|
+
# Determine if the service call has successfully ran
|
204
|
+
sig { returns(T::Boolean) }
|
205
|
+
def success?; end
|
206
|
+
|
207
|
+
# Return if the service has any issues (errors or failure)
|
208
|
+
sig { returns(T::Boolean) }
|
209
|
+
def issues?; end
|
210
|
+
|
211
|
+
# Returns the value of attribute called
|
212
|
+
sig { returns(Object) }
|
213
|
+
attr_accessor :called
|
214
|
+
|
215
|
+
# Returns the value of attribute errors
|
216
|
+
sig { returns(Object) }
|
217
|
+
attr_accessor :errors
|
218
|
+
|
219
|
+
# Returns the value of attribute failure
|
220
|
+
sig { returns(Object) }
|
221
|
+
attr_accessor :failure
|
222
|
+
|
223
|
+
# Returns the value of attribute results
|
224
|
+
sig { returns(Object) }
|
225
|
+
attr_accessor :results
|
226
|
+
end
|
227
|
+
|
228
|
+
# Callbacks before/after the services `call` is called
|
229
|
+
module Callbacks
|
230
|
+
# Used to include/extend modules into the current class
|
231
|
+
#
|
232
|
+
# _@param_ `base`
|
233
|
+
sig { params(base: Object).void }
|
234
|
+
def self.included(base); end
|
235
|
+
|
236
|
+
# Methods to load in the service object as class methods
|
237
|
+
module ClassMethods
|
238
|
+
# Add callback `before` method or block
|
239
|
+
#
|
240
|
+
# _@param_ `callbacks`
|
241
|
+
sig { params(callbacks: T::Hash[T.untyped, T.untyped], block: T.untyped).void }
|
242
|
+
def before(*callbacks, &block); end
|
243
|
+
|
244
|
+
# Add callback `after` method or block
|
245
|
+
#
|
246
|
+
# _@param_ `callbacks`
|
247
|
+
sig { params(callbacks: T::Hash[T.untyped, T.untyped], block: T.untyped).void }
|
248
|
+
def after(*callbacks, &block); end
|
249
|
+
|
250
|
+
# Call all callbacks before `#call` is referenced (methods, blocks, etc.)
|
251
|
+
sig { void }
|
252
|
+
def run_before_callbacks; end
|
253
|
+
|
254
|
+
# Call and run all callbacks after `#call` has ran
|
255
|
+
sig { void }
|
256
|
+
def run_after_callbacks; end
|
257
|
+
|
258
|
+
# Create a list of `after` callback methods and/or blocks
|
259
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
260
|
+
def after_callbacks; end
|
261
|
+
|
262
|
+
# Create a list of `before` callback methods and/or blocks
|
263
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
264
|
+
def before_callbacks; end
|
265
|
+
|
266
|
+
# Loopthrough and run all the classes listed callback methods
|
267
|
+
#
|
268
|
+
# _@param_ `callbacks` — a list of methods to be called
|
269
|
+
sig { params(callbacks: T::Array[T.any(String, Symbol)]).void }
|
270
|
+
def run_callbacks(callbacks); end
|
271
|
+
|
272
|
+
# Run callback m method or block
|
273
|
+
#
|
274
|
+
# _@param_ `callback`
|
275
|
+
#
|
276
|
+
# _@param_ `options`
|
277
|
+
sig { params(callback: Symbol, options: T::Hash[T.untyped, T.untyped]).void }
|
278
|
+
def run_callback(callback, *options); end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# Used to generate a Rails service object
|
284
|
+
class CrossbeamGenerator < Rails::Generators::Base
|
285
|
+
sig { void }
|
286
|
+
def generate_service; end
|
287
|
+
|
288
|
+
sig { void }
|
289
|
+
def generate_test; end
|
290
|
+
|
291
|
+
# Returns a string to use as the service classes file name
|
292
|
+
sig { returns(String) }
|
293
|
+
def filename; end
|
294
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './error'
|
4
|
+
|
5
|
+
module Crossbeam
|
6
|
+
# Callbacks before/after the services `call` is called
|
7
|
+
module Callbacks
|
8
|
+
# Used to include/extend modules into the current class
|
9
|
+
#
|
10
|
+
# @param base [Object]
|
11
|
+
# @return [void]
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
extend(ClassMethods)
|
15
|
+
|
16
|
+
attr_accessor :klass
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Methods to load in the service object as class methods
|
21
|
+
module ClassMethods
|
22
|
+
# Add callback `before` method or block
|
23
|
+
#
|
24
|
+
# @param callbacks [Hash]
|
25
|
+
# @return [void]
|
26
|
+
# @yield An optional block to instance_exec(&block) || instance_eval(&block)
|
27
|
+
def before(*callbacks, &block)
|
28
|
+
callbacks << block if block
|
29
|
+
callbacks.each { |callback| before_callbacks << callback }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add callback `after` method or block
|
33
|
+
#
|
34
|
+
# @param callbacks [Hash]
|
35
|
+
# @return [void]
|
36
|
+
# @yield An optional block to instance_exec(&block) || instance_eval(&block)
|
37
|
+
def after(*callbacks, &block)
|
38
|
+
callbacks << block if block
|
39
|
+
callbacks.each { |callback| after_callbacks << callback }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Call all callbacks before `#call` is referenced (methods, blocks, etc.)
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
def run_before_callbacks
|
46
|
+
# run_callbacks(self.class.before_callbacks)
|
47
|
+
run_callbacks(before_callbacks)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Call and run all callbacks after `#call` has ran
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
def run_after_callbacks
|
54
|
+
run_callbacks(after_callbacks)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a list of `after` callback methods and/or blocks
|
58
|
+
#
|
59
|
+
# @return [Hash]
|
60
|
+
def after_callbacks
|
61
|
+
@after_callbacks ||= []
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create a list of `before` callback methods and/or blocks
|
65
|
+
#
|
66
|
+
# @return [Hash]
|
67
|
+
def before_callbacks
|
68
|
+
@before_callbacks ||= []
|
69
|
+
end
|
70
|
+
|
71
|
+
# Loopthrough and run all the classes listed callback methods
|
72
|
+
#
|
73
|
+
# @param callbacks [Array<String, Symbol>] a list of methods to be called
|
74
|
+
# @return [void]
|
75
|
+
def run_callbacks(callbacks)
|
76
|
+
callbacks.each { |callback| run_callback(callback) }
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Run callback m method or block
|
82
|
+
#
|
83
|
+
# @param callback [Symbol]
|
84
|
+
# @param options [Hash]
|
85
|
+
# @return [void]
|
86
|
+
def run_callback(callback, *options)
|
87
|
+
# Ensure the initialize instance class has been called and passed
|
88
|
+
return unless @klass
|
89
|
+
|
90
|
+
# callback.is_a?(Symbol) ? send(callback, *options) : instance_exec(*options, &callback)
|
91
|
+
if [String, Symbol].include?(callback.class) && @klass.respond_to?(callback.to_sym)
|
92
|
+
@klass.send(callback, *options)
|
93
|
+
elsif callback.is_a?(Proc)
|
94
|
+
@klass.instance_exec(*options, &callback)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Crossbeam
|
4
|
+
# Error message that is raised to flag a Crossbeam service error
|
5
|
+
# used to create error messages for Crossbeam
|
6
|
+
# @private
|
7
|
+
class Error < StandardError
|
8
|
+
# @return [String] Error message associated with StandardError
|
9
|
+
attr_reader :context
|
10
|
+
|
11
|
+
# Used to initializee an error message
|
12
|
+
#
|
13
|
+
# @param context [String, Hash]
|
14
|
+
# @return [Object]
|
15
|
+
def initialize(context = nil)
|
16
|
+
@context = context
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Used to generate `ArguementError` exception
|
22
|
+
# @private
|
23
|
+
class ArguementError < Error; end
|
24
|
+
# Used to generate `Failure` exception
|
25
|
+
# @private
|
26
|
+
class Failure < Error; end
|
27
|
+
# Used to generate `NotImplementedError` exception
|
28
|
+
# @private
|
29
|
+
class NotImplementedError < Error; end
|
30
|
+
# Used to generate `UndefinedMethod` exception
|
31
|
+
# @private
|
32
|
+
class UndefinedMethod < Error; end
|
33
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './error'
|
4
|
+
|
5
|
+
module Crossbeam
|
6
|
+
# Very similar to ActiveModel errors
|
7
|
+
# https://github.com/rails/rails/blob/main/activemodel/lib/active_model/validations.rb
|
8
|
+
|
9
|
+
# Used to allow adding errors to the service call similar to ActiveRecord errors
|
10
|
+
class Errors < Hash
|
11
|
+
# Add an error to the list of errors
|
12
|
+
#
|
13
|
+
# @param key [String, Symbol] the key/attribute for the error. (Usually ends up being :base)
|
14
|
+
# @param value [String, Symbol] a description of the error
|
15
|
+
# @param _opts [Hash] additional attributes that get ignored
|
16
|
+
# @return [Hash]
|
17
|
+
def add(key, value, _opts = {})
|
18
|
+
self[key] ||= []
|
19
|
+
self[key] << value
|
20
|
+
self[key].uniq!
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add multiple errors to the error hash
|
24
|
+
#
|
25
|
+
# @param errors [Hash<String, Symbol>]
|
26
|
+
# @return [Hash], Array]
|
27
|
+
def add_multiple_errors(errors)
|
28
|
+
errors.each do |key, values|
|
29
|
+
if values.is_a?(String)
|
30
|
+
add(key, values)
|
31
|
+
elsif [Array, Hash].include?(values.class)
|
32
|
+
values.each { |value| add(key, value) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Look through and return a list of all errorr messages
|
38
|
+
#
|
39
|
+
# @return [Hash, Array]
|
40
|
+
def each
|
41
|
+
each_key do |field|
|
42
|
+
self[field].each { |message| yield field, message }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return a full list of error messages
|
47
|
+
#
|
48
|
+
# @return [Array<String>]
|
49
|
+
def full_messages
|
50
|
+
map { |attribute, message| full_message(attribute, message) }.freeze
|
51
|
+
end
|
52
|
+
|
53
|
+
# Used to convert the list of errors to a string
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def to_s
|
57
|
+
return '' unless self&.any?
|
58
|
+
|
59
|
+
full_messages.join("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Convert the message to a full error message
|
65
|
+
#
|
66
|
+
# @param attribute [String, Symbol]
|
67
|
+
# @param message [String]
|
68
|
+
# @return [String]
|
69
|
+
def full_message(attribute, message)
|
70
|
+
return message if attribute == :base
|
71
|
+
|
72
|
+
attr_name = attribute.to_s.tr('.', '_').capitalize
|
73
|
+
format('%<attr>s %<msg>s', attr: attr_name, msg: message)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './error'
|
4
|
+
|
5
|
+
module Crossbeam
|
6
|
+
# For forcing specific output after `#call`
|
7
|
+
module Output
|
8
|
+
# Used to include/extend modules into the current class
|
9
|
+
#
|
10
|
+
# @param base [Object]
|
11
|
+
# @return [void]
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
extend(ClassMethods)
|
15
|
+
|
16
|
+
attr_accessor :output_param
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Methods to load in the service object as class methods
|
21
|
+
module ClassMethods
|
22
|
+
# @return [Hash]
|
23
|
+
CB_ALLOWED_OUTPUTS = [NilClass, String, Symbol].freeze
|
24
|
+
# Used to specify an attribute/instance variable that should be used too return instead of @result
|
25
|
+
#
|
26
|
+
# @param param [String, Symbol]
|
27
|
+
# @return [String, Symbol]
|
28
|
+
def output(param)
|
29
|
+
raise(ArguementError, 'A string or symbol is require for output') unless allowed_output?(param)
|
30
|
+
|
31
|
+
@output_param = param
|
32
|
+
end
|
33
|
+
|
34
|
+
# Determine if the output attribute type is allowed
|
35
|
+
#
|
36
|
+
# @param output_type [String]
|
37
|
+
# @return [Boolean]
|
38
|
+
def allowed_output?(output_type)
|
39
|
+
CB_ALLOWED_OUTPUTS.include?(output_type.class)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Used to determine if a output parameter has been set or not
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
def output?
|
46
|
+
!output_param.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determine the output to return if the instance_variable exists in the klass
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def set_results_output
|
53
|
+
return unless @klass
|
54
|
+
return unless output? && @klass.instance_variable_defined?("@#{output_param}")
|
55
|
+
|
56
|
+
@result.results = @klass.instance_variable_get("@#{output_param}")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add errors to @result.errors
|
60
|
+
#
|
61
|
+
# @return [Void]
|
62
|
+
def build_error_list
|
63
|
+
return unless @klass && @klass&.errors&.any?
|
64
|
+
|
65
|
+
@klass.errors.each do |error|
|
66
|
+
# options is usually passed with an ActiveRecord validation error
|
67
|
+
# @example:
|
68
|
+
# <attribute=age, type=greater_than_or_equal_to, options={:value=>15, :count=>18}>
|
69
|
+
@result.errors.add(error.attribute, error.message)
|
70
|
+
end
|
71
|
+
@klass.errors.clear
|
72
|
+
reassign_results
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reassign result, unless errors
|
76
|
+
#
|
77
|
+
# @return [Void]
|
78
|
+
def reassign_results
|
79
|
+
@result.results = nil
|
80
|
+
@result.results = @klass.instance_variable_get("@#{output_param}") if specified_output?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Does the klass have an assigned output
|
84
|
+
#
|
85
|
+
# @return [Boolean]
|
86
|
+
def specified_output?
|
87
|
+
output? && @klass.instance_variable_defined?("@#{output_param}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Used hold the parameter which can/will be used instead of @result
|
91
|
+
#
|
92
|
+
# @return [String, Symbol, nil]
|
93
|
+
def output_param
|
94
|
+
@output_param ||= nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
# Crossbeam files
|
5
|
+
require_relative './error'
|
6
|
+
require_relative './errors'
|
7
|
+
|
8
|
+
module Crossbeam
|
9
|
+
# Used as a data container to hold a service calls results, errors, etc.
|
10
|
+
# class Result < Struct.new(:called, :errors, :failure, :results)
|
11
|
+
Result = Struct.new(:called, :errors, :failure, :results) do
|
12
|
+
# extend ActiveModel::Naming
|
13
|
+
# include ActiveModel::Conversion
|
14
|
+
# attr_accessor :called, :errors, :failure, :results
|
15
|
+
|
16
|
+
# Used to initialize a service calls results, errors, and stats
|
17
|
+
#
|
18
|
+
# @param called [Boolean]
|
19
|
+
# @param errors [Hash]
|
20
|
+
# @param failure [Boolean]
|
21
|
+
# @param results [Object]
|
22
|
+
def initialize(called: false, errors: nil, failure: false, results: nil)
|
23
|
+
super(called, errors, failure, results)
|
24
|
+
|
25
|
+
self.called = called
|
26
|
+
self.errors = Crossbeam::Errors.new
|
27
|
+
self.failure = failure
|
28
|
+
self.results = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# The serivce can't officially be a failure/pass if it hasn't been "called"
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
def called?
|
35
|
+
called || false
|
36
|
+
end
|
37
|
+
|
38
|
+
# Determine if the service call has any errors
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
def errors?
|
42
|
+
(errors.is_a?(Hash) && errors.any?) || false
|
43
|
+
end
|
44
|
+
|
45
|
+
# Determine if the service call has failed to uccessfully complete
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
def failure?
|
49
|
+
called? && issues?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Determine if the service call has successfully ran
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def success?
|
56
|
+
called? && !issues?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Return if the service has any issues (errors or failure)
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
def issues?
|
65
|
+
errors? || failure
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/crossbeam.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
require 'dry-initializer'
|
5
|
+
|
6
|
+
# Crossbeam required modules
|
7
|
+
require_relative 'crossbeam/callbacks'
|
8
|
+
require_relative 'crossbeam/error' # exceptions
|
9
|
+
require_relative 'crossbeam/errors' # errors
|
10
|
+
# For forcing specific output after `#call`
|
11
|
+
require_relative 'crossbeam/output'
|
12
|
+
require_relative 'crossbeam/result'
|
13
|
+
require_relative 'crossbeam/version'
|
14
|
+
|
15
|
+
require 'debug'
|
16
|
+
|
17
|
+
# Crossbeam module to include with your service classes
|
18
|
+
module Crossbeam
|
19
|
+
# Used to include/extend modules into the current class
|
20
|
+
#
|
21
|
+
# @param base [Object]
|
22
|
+
# @return [void]
|
23
|
+
def self.included(base)
|
24
|
+
base.class_eval do
|
25
|
+
include(ActiveModel::Validations) # Used to add validation errors to a class
|
26
|
+
extend(Dry::Initializer) # Used to allow for easy attribute initializations
|
27
|
+
extend(ClassMethods) # Class methods used to initialize the service call
|
28
|
+
include(Output)
|
29
|
+
include(Callbacks) # Callbacks that get called before/after `.call` is referenced
|
30
|
+
|
31
|
+
# Holds the service call results and current class call for Crossbeam Instance
|
32
|
+
attr_reader :result, :klass
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Methods to load in the service object as class methods
|
37
|
+
module ClassMethods
|
38
|
+
# Used to initiate and service call
|
39
|
+
#
|
40
|
+
# @param params [Array<Object>]
|
41
|
+
# @param options [Hash<Symbol, Object>]
|
42
|
+
# @return [Struct, Object]
|
43
|
+
def call(*params, **options, &block)
|
44
|
+
@result = Crossbeam::Result.new(called: true)
|
45
|
+
@klass = new(*params, **options)
|
46
|
+
run_callbacks_and_klass(&block)
|
47
|
+
@result
|
48
|
+
rescue Crossbeam::Failure => e
|
49
|
+
process_error(e)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Call the klass's before/after callbacks, process errors, and call @klass
|
53
|
+
#
|
54
|
+
# @return [Void]
|
55
|
+
def run_callbacks_and_klass(&block)
|
56
|
+
# Call all the classes `before`` callbacks
|
57
|
+
run_before_callbacks
|
58
|
+
# Initialize and run the classes call method
|
59
|
+
@result.results = @klass.call(&block)
|
60
|
+
# build @result.errors if errors were added
|
61
|
+
build_error_list if @klass.errors.any?
|
62
|
+
# re-assign @result if output was set
|
63
|
+
set_results_output
|
64
|
+
# Call all the classes `after` callbacks
|
65
|
+
run_after_callbacks
|
66
|
+
end
|
67
|
+
|
68
|
+
# Process the error that was thrown by the object call
|
69
|
+
#
|
70
|
+
# @param error [Object] error generated by the object call
|
71
|
+
# @return [Object, Crossbeam::Result]
|
72
|
+
def process_error(error)
|
73
|
+
@result.failure = true
|
74
|
+
@result.errors.add(:failure, error.to_s)
|
75
|
+
@result.results = nil
|
76
|
+
@result
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Force the job to raise an exception and stop the rest of the service call
|
81
|
+
#
|
82
|
+
# @param error [String]
|
83
|
+
# @return [void]
|
84
|
+
def fail!(error)
|
85
|
+
raise(Crossbeam::Failure, error)
|
86
|
+
end
|
87
|
+
|
88
|
+
%w[called failure errors issues success].each do |attr|
|
89
|
+
# Used to determine the current state of the service call
|
90
|
+
#
|
91
|
+
# @return [Boolean]
|
92
|
+
define_method("#{attr}?") do
|
93
|
+
return false unless @result
|
94
|
+
|
95
|
+
attr = attr.to_s
|
96
|
+
@result.send("#{attr}?".to_sym) || false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Used to return a list of errors for the current call
|
101
|
+
#
|
102
|
+
# @return [Hash]
|
103
|
+
def errors
|
104
|
+
return {} unless @result
|
105
|
+
|
106
|
+
@result.errors
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Only define the generators if part of rails app to prevent causing an excceeption
|
4
|
+
if defined?(Rails)
|
5
|
+
require 'rails/generators'
|
6
|
+
|
7
|
+
# Used to generate a Rails service object
|
8
|
+
class CrossbeamGenerator < ::Rails::Generators::Base
|
9
|
+
source_root File.expand_path(File.join('.', 'templates'), File.dirname(__FILE__))
|
10
|
+
|
11
|
+
argument :class_name, type: :string
|
12
|
+
argument :attributes, type: :array, default: [], banner: 'field[:type]'
|
13
|
+
desc 'Generate a Crossbeam service class'
|
14
|
+
|
15
|
+
# @return [void]
|
16
|
+
def generate_service
|
17
|
+
template 'service_class.rb.tt', "app/services/#{filename}.rb", force: true
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [void]
|
21
|
+
def generate_test
|
22
|
+
return unless Rails&.application&.config&.generators&.test_framework == :rspec
|
23
|
+
|
24
|
+
template 'service_spec.rb.tt', "spec/services/#{filename}_spec.rb", force: true
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Returns a string to use as the service classes file name
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
def filename
|
33
|
+
class_name.underscore
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crossbeam
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brandon Hicks
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-initializer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-performance
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: An easy way to create and run service objects with callbacks, validations,
|
98
|
+
errors, and responses
|
99
|
+
email:
|
100
|
+
- tarellel@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files:
|
104
|
+
- CHANGELOG.md
|
105
|
+
- README.md
|
106
|
+
- LICENSE.txt
|
107
|
+
files:
|
108
|
+
- CHANGELOG.md
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- defs.rbi
|
112
|
+
- lib/crossbeam.rb
|
113
|
+
- lib/crossbeam/callbacks.rb
|
114
|
+
- lib/crossbeam/error.rb
|
115
|
+
- lib/crossbeam/errors.rb
|
116
|
+
- lib/crossbeam/output.rb
|
117
|
+
- lib/crossbeam/result.rb
|
118
|
+
- lib/crossbeam/version.rb
|
119
|
+
- lib/generators/crossbeam_generator.rb
|
120
|
+
- lib/generators/templates/service_class.rb.tt
|
121
|
+
- lib/generators/templates/service_spec.rb.tt
|
122
|
+
homepage: https://github.com/tarellel/crossbeam
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata:
|
126
|
+
bug_tracker_uri: https://github.com/tarellel/crossbeam/issue
|
127
|
+
changelog_uri: https://github.com/tarellel/crossbeam/blob/master/CHANGELOG.md
|
128
|
+
homepage_uri: https://github.com/tarellel/crossbeam
|
129
|
+
source_code_uri: https://github.com/tarellel/crossbeam
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2.7'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubygems_version: 3.3.15
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: An easy way to create and run service objects with callbacks, validations,
|
149
|
+
errors, and responses
|
150
|
+
test_files: []
|