lite-command 2.1.0 → 2.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efd37a3a2fcbb8d90fef73088313f18bd281922d46594a4a57ee8dd34fcaf801
4
- data.tar.gz: 4e88ca10b986095be52c3e454e70a6c8d7b3556bb4770ed51aca80d0b9beb894
3
+ metadata.gz: a80b87ce018b8d2c4097c6a3c1e22869b51089e7bb1e040f8b4be33f72a0cc47
4
+ data.tar.gz: 8db4399804fbdcd1ef0a42baa4ae902d864b8283cdb26ea0b8fdbac123f569c8
5
5
  SHA512:
6
- metadata.gz: 4123ef0a3315a8b00796a58ca65b10bc2a51905dc59e663b1ffef2ec7f9becc700ae64216cb896024564993749979cd824087a68bac1e95532311dba29cd7aa0
7
- data.tar.gz: dd4e22ead3d68f981319a372a0ce305f734f1745a31223daf725d93ff71cccbe1b4708e7b3d7ef4921f98f2e8df6c04939855b8a7e8d0e3292f5e7f3e0ec6c1a
6
+ metadata.gz: a00f4af0af22973c3ec4fa43e635d7bca891b97a3cebb264b52955dd1a9e9b963045297569cae7a6144820cb3cea0907044b73200f1f9a0781befded2fd1e0c1
7
+ data.tar.gz: 7a0239e49c62c900b1df6c39e0d7a6c97ac7768bb0e6b8b1292b44726a59e5f336a9d562c20fd4979f9950c1e7c3a57528ae2d83e5e85aee34379a75fdfbe1ce
@@ -0,0 +1,30 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+ branches:
8
+ - master
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby:
15
+ - '3.3'
16
+ - '3.2'
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+ - name: Install
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{matrix.ruby}}
24
+ bundler-cache: true
25
+ - name: RSpec
26
+ run: bundle exec rspec .
27
+ - name: Fasterer
28
+ run: bundle exec fasterer .
29
+ - name: Rubocop
30
+ run: bundle exec rubocop .
data/CHANGELOG.md CHANGED
@@ -6,13 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.1.2] - 2024-10-08
10
+ ### Added
11
+ - Allow `filled` to pass `{ empty: false }` to check if value is empty
12
+
13
+ ## [2.1.1] - 2024-10-06
14
+ ### Added
15
+ - Added on_status hook to `execute!`
16
+ ### Changed
17
+ - Reuse same attribute instance
18
+
9
19
  ## [2.1.0] - 2024-10-05
10
20
  ### Added
11
21
  - Added passing metadata to faults
12
22
  - Added `on_success` callback
13
23
  - Added `on_pending`, `on_executing`, `on_complete`, and `on_interrupted` callbacks
14
24
  - Added attributes and attribute validations
15
- - Added sequences
25
+ - Added steps and sequences
26
+ - Added fault streamer
16
27
  ### Changed
17
28
  - Check error descendency instead of type
18
29
  - Rename internal modules
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lite-command (2.1.0)
4
+ lite-command (2.1.2)
5
5
  ostruct
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -8,9 +8,9 @@ Lite::Command provides an API for building simple and complex command based serv
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
11
- > [!NOTE]
12
- > Gem versions `2.0.0`, `2.0.1`, `2.0.2`, and `2.0.3` are borked.
13
- > Version `2.1.0` is the latest working version.
11
+ > [!WARNING]
12
+ > Gem versions `~> 2.0` are borked.
13
+ > Version `~> 2.1.0` is the suggested working version.
14
14
 
15
15
  ```ruby
16
16
  gem 'lite-command'
@@ -48,7 +48,8 @@ Or install it yourself as:
48
48
 
49
49
  ## Setup
50
50
 
51
- Defining a command is as simple as adding a call method to a command object (required).
51
+ Defining a command is as simple as inheriting the base class and
52
+ adding a `call` method to a command object (required).
52
53
 
53
54
  ```ruby
54
55
  class CalculatePower < Lite::Command::Base
@@ -133,7 +134,7 @@ class CalculatePower < Lite::Command::Base
133
134
  end
134
135
 
135
136
  CalculatePower.call!(...)
136
- #=> raises CalculatePower::Fault
137
+ #=> raises CalculatePower::Failure
137
138
  ```
138
139
 
139
140
  ## Context
@@ -142,6 +143,9 @@ Accessing the call arguments can be done through its internal context.
142
143
  It can be used as internal storage to be accessed by it self and any
143
144
  of its children commands.
144
145
 
146
+ > [!NOTE]
147
+ > Attributes that do **NOT** exist on the context will return `nil`.
148
+
145
149
  ```ruby
146
150
  class CalculatePower < Lite::Command::Base
147
151
 
@@ -152,8 +156,9 @@ class CalculatePower < Lite::Command::Base
152
156
 
153
157
  end
154
158
 
155
- command = CalculatePower.call(a: 2, b: 3)
156
- command.context.result #=> 8
159
+ cmd = CalculatePower.call(a: 2, b: 3)
160
+ cmd.context.result #=> 8
161
+ cmd.ctx.fake #=> nil
157
162
  ```
158
163
 
159
164
  ### Attributes
@@ -167,7 +172,7 @@ method which automatically delegates to `context`.
167
172
  | `from` | Symbol, String | `:context` | The object containing the attribute. |
168
173
  | `types` | Symbol, String, Array, Proc | | The allowed class types of the attribute value. |
169
174
  | `required` | Symbol, String, Boolean, Proc | `false` | The attribute must be passed to the context or delegatable (no matter the value). |
170
- | `filled` | Symbol, String, Boolean, Proc | `false` | The attribute value must be not be `nil`. |
175
+ | `filled` | Symbol, String, Boolean, Proc, Hash | `false` | The attribute value must be not be `nil`. Prevent empty values using `{ empty: false }` |
171
176
 
172
177
  > [!NOTE]
173
178
  > If optioned with some similar to `filled: true, types: [String, NilClass]`
@@ -180,7 +185,7 @@ class CalculatePower < Lite::Command::Base
180
185
 
181
186
  attribute :a, :b
182
187
  attribute :c, :d, from: :remote_storage, types: [Integer, Float]
183
- attribute :x, :y, from: :local_storage, if: :signed_in?
188
+ attribute :x, :y, from: :local_storage, filled: { empty: false }, if: :signed_in?
184
189
 
185
190
  def call
186
191
  context.result =
@@ -202,20 +207,20 @@ class CalculatePower < Lite::Command::Base
202
207
  end
203
208
 
204
209
  # With valid options:
205
- storage = RemoteStorage.new(c: 2, d: 2, j: 99)
206
- command = CalculatePower.call(a: 2, b: 2, remote_storage: storage)
207
- command.status #=> "success"
208
- command.context.result #=> 6
209
-
210
- # With invalid options
211
- command = CalculatePower.call
212
- command.status #=> "invalid"
213
- command.reason #=> "Invalid context attributes"
214
- command.metadata #=> {
215
- #=> context: ["a is required", "remote_storage must be filled"],
216
- #=> remote_storage: ["d type invalid"]
217
- #=> local_storage: ["is not defined or an attribute"]
218
- #=> }
210
+ rs = RemoteStorage.new(c: 2, d: 2, j: 99)
211
+ cmd = CalculatePower.call(a: 2, b: 2, remote_storage: rs)
212
+ cmd.status #=> "success"
213
+ cmd.context.result #=> 6
214
+
215
+ # With invalid options:
216
+ cmd = CalculatePower.call
217
+ cmd.status #=> "invalid"
218
+ cmd.reason #=> "Invalid context attributes"
219
+ cmd.metadata #=> {
220
+ #=> context: ["a is required", "remote_storage must be filled"],
221
+ #=> remote_storage: ["d type invalid"]
222
+ #=> local_storage: ["is not defined or an attribute"]
223
+ #=> }
219
224
  ```
220
225
 
221
226
  ## States
@@ -232,18 +237,16 @@ command.metadata #=> {
232
237
  > States are automatically transitioned and should **NEVER** be altered manually.
233
238
 
234
239
  ```ruby
235
- class CalculatePower < Lite::Command::Base
240
+ cmd = CalculatePower.call
241
+ cmd.state #=> "complete"
236
242
 
237
- def call
238
- # ...
239
- end
240
-
241
- end
243
+ cmd.pending? #=> false
244
+ cmd.executing? #=> false
245
+ cmd.complete? #=> true
246
+ cmd.interrupted? #=> false
242
247
 
243
- command = CalculatePower.call(a: 1, b: 3)
244
- command.state #=> "executed"
245
- command.pending? #=> false
246
- command.executed? #=> false
248
+ # `complete` or `interrupted`
249
+ cmd.executed?
247
250
  ```
248
251
 
249
252
  ## Statuses
@@ -285,14 +288,25 @@ class CalculatePower < Lite::Command::Base
285
288
 
286
289
  end
287
290
 
288
- command = CalculatePower.call(a: 1, b: 3)
289
- command.ctx.result #=> nil
290
- command.status #=> "noop"
291
- command.reason #=> "Anything to the power of 1 is 1"
292
- command.metadata #=> { i18n: "some.key" }
293
- command.invalid? #=> false
294
- command.noop? #=> true
295
- command.noop?("Anything to the power of 1 is 1") #=> true
291
+ cmd = CalculatePower.call(a: 1, b: 3)
292
+ cmd.status #=> "noop"
293
+ cmd.reason #=> "Anything to the power of 1 is 1"
294
+ cmd.metadata #=> { i18n: "some.key" }
295
+
296
+ cmd.success? #=> false
297
+ cmd.noop? #=> true
298
+ cmd.noop?("Other reason") #=> false
299
+ cmd.invalid? #=> false
300
+ cmd.failure? #=> false
301
+ cmd.error? #=> false
302
+
303
+ # `success` or `noop`
304
+ cmd.ok? #=> true
305
+ cmd.ok?("Other reason") #=> false
306
+
307
+ # NOT `success`
308
+ cmd.fault? #=> true
309
+ cmd.fault?("Other reason") #=> false
296
310
  ```
297
311
 
298
312
  ## Callbacks
@@ -423,7 +437,8 @@ that it gains automated indexing and the parents `cmd_id`.
423
437
  class CalculatePower < Lite::Command::Base
424
438
 
425
439
  def call
426
- CalculateSqrt.call(context.merge!(some_other: "required value"))
440
+ context.merge!(some_other: "required value")
441
+ CalculateSqrt.call(context)
427
442
  end
428
443
 
429
444
  end
@@ -434,7 +449,8 @@ end
434
449
  Throwing faults allows you to bubble up child faults up to the parent.
435
450
  Use it to create branches within your logic and create clean tracing
436
451
  of your command results. You can use `throw!` as a catch-all or any
437
- of the bang status method `failure!`.
452
+ of the bang status method `failure!`. Any `reason` and `metadata` will
453
+ be bubbled up from the original fault.
438
454
 
439
455
  ```ruby
440
456
  class CalculatePower < Lite::Command::Base
@@ -443,10 +459,10 @@ class CalculatePower < Lite::Command::Base
443
459
  command = CalculateSqrt.call(context.merge!(some_other: "required value"))
444
460
 
445
461
  if command.noop?("Sqrt of 1 is 1")
446
- # Manually throw any fault you want
462
+ # Manually throw a specific fault
447
463
  invalid!(command)
448
464
  elsif command.fault?
449
- # Automatically throws a matching fault type
465
+ # Automatically throws a matching fault
450
466
  throw!(command)
451
467
  else
452
468
  # Success, do nothing
@@ -467,6 +483,10 @@ This is useful for composing multiple steps into one call.
467
483
  > so its no different than just passing the context forward. To change
468
484
  > this behavior, just override the `ok?` method with you logic, eg: just `success`
469
485
 
486
+ > [!IMPORTANT]
487
+ > Do **NOT** define a call method in this class. The sequence logic is
488
+ > automatically defined by the sequence class.
489
+
470
490
  ```ruby
471
491
  class ProcessCheckout < Lite::Command::Sequence
472
492
 
@@ -477,8 +497,7 @@ class ProcessCheckout < Lite::Command::Sequence
477
497
  step SendConfirmationEmail, SendConfirmationText
478
498
  step NotifyWarehouse, unless: proc { ctx.invoice.fullfilled_by_amazon? }
479
499
 
480
- # Do NOT set a call method.
481
- # Its defined by Lite::Command::Sequence
500
+ # Do NOT define a call method.
482
501
 
483
502
  private
484
503
 
@@ -488,7 +507,7 @@ class ProcessCheckout < Lite::Command::Sequence
488
507
 
489
508
  end
490
509
 
491
- sequence = ProcessCheckout.call(...)
510
+ seq = ProcessCheckout.call(...)
492
511
  # <ProcessCheckout ...>
493
512
  ```
494
513
 
@@ -4,12 +4,10 @@ module Lite
4
4
  module Command
5
5
  class Attribute
6
6
 
7
- # TODO: allow procs
7
+ attr_accessor :command
8
+ attr_reader :method_name, :options, :errors
8
9
 
9
- attr_reader :command, :method_name, :options, :errors
10
-
11
- def initialize(command, method_name, options)
12
- @command = command
10
+ def initialize(method_name, options)
13
11
  @method_name = method_name
14
12
  @options = options
15
13
  @errors = []
@@ -36,7 +34,7 @@ module Lite
36
34
  t = Array(Utils.call(command, options[:types]))
37
35
 
38
36
  if filled?
39
- t - [NilClass]
37
+ t.uniq - [NilClass]
40
38
  else
41
39
  t | [NilClass]
42
40
  end
@@ -84,9 +82,17 @@ module Lite
84
82
  @errors << "#{method_name} type invalid"
85
83
  end
86
84
 
85
+ def empty?
86
+ r = Utils.try(options[:filled], :[], :empty)
87
+ return if r.nil? || r == true
88
+ return unless value.respond_to?(:empty?)
89
+
90
+ value.empty?
91
+ end
92
+
87
93
  def validate_attribute_filled!
88
94
  return unless filled?
89
- return unless value.nil?
95
+ return unless value.nil? || empty?
90
96
 
91
97
  @errors << "#{method_name} must be filled"
92
98
  end
@@ -12,15 +12,14 @@ module Lite
12
12
 
13
13
  def attributes
14
14
  @attributes ||=
15
- command.class.attributes.map do |method_name, options|
16
- Lite::Command::Attribute.new(command, method_name, options)
15
+ command.class.attributes.map do |_method_name, attribute|
16
+ attribute.tap { |a| a.command = command }
17
17
  end
18
18
  end
19
19
 
20
20
  def errors
21
21
  @errors ||= attributes.each_with_object({}) do |attribute, h|
22
- attribute.validate!
23
- next if attribute.valid?
22
+ next if attribute.tap(&:validate!).valid?
24
23
 
25
24
  h[attribute.from] ||= []
26
25
  h[attribute.from] = h[attribute.from] | attribute.errors
@@ -86,7 +86,6 @@ module Lite
86
86
  # eg: invalid!("idk") or failure!(fault) or error!("idk", { error_key: "some.error" })
87
87
  define_method(:"#{f}!") do |object, metadata = nil|
88
88
  fault(object, f, metadata)
89
-
90
89
  raise Lite::Command::Fault.build(f.capitalize, self, object, dynamic: raise_dynamic_faults?)
91
90
  end
92
91
  end
@@ -13,13 +13,13 @@ module Lite
13
13
 
14
14
  def attribute(*args, **options)
15
15
  args.each do |method_name|
16
- attributes[method_name] = options
16
+ attribute = Lite::Command::Attribute.new(method_name, options)
17
+ attributes[method_name] = attribute
17
18
 
18
19
  define_method(method_name) do
19
20
  ivar = :"@#{method_name}"
20
21
  return instance_variable_get(ivar) if instance_variable_defined?(ivar)
21
22
 
22
- attribute = Lite::Command::Attribute.new(self, method_name, options)
23
23
  instance_variable_set(ivar, attribute.value)
24
24
  end
25
25
  end
@@ -72,6 +72,7 @@ module Lite
72
72
  rescue StandardError => e
73
73
  fault(e, ERROR, metadata) unless e.is_a?(Lite::Command::Fault)
74
74
  after_execution
75
+ Utils.hook(self, :"on_#{status}", e)
75
76
  raise(e)
76
77
  else
77
78
  Utils.hook(self, :"on_#{state}")
@@ -16,13 +16,13 @@ module Lite
16
16
  try(object, method_name, *args, include_private: true)
17
17
  end
18
18
 
19
- def call(object, method_name_or_proc)
20
- if method_name_or_proc.is_a?(Symbol) || method_name_or_proc.is_a?(String)
21
- object.send(method_name_or_proc)
22
- elsif method_name_or_proc.is_a?(Proc)
23
- object.instance_eval(&method_name_or_proc)
19
+ def call(object, argument)
20
+ if argument.is_a?(Symbol) || argument.is_a?(String)
21
+ object.send(argument)
22
+ elsif argument.is_a?(Proc)
23
+ object.instance_eval(&argument)
24
24
  else
25
- method_name_or_proc
25
+ argument
26
26
  end
27
27
  end
28
28
 
@@ -3,7 +3,7 @@
3
3
  module Lite
4
4
  module Command
5
5
 
6
- VERSION = "2.1.0"
6
+ VERSION = "2.1.2"
7
7
 
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lite-command
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-06 00:00:00.000000000 Z
11
+ date: 2024-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ostruct
@@ -173,10 +173,10 @@ extra_rdoc_files: []
173
173
  files:
174
174
  - ".fasterer.yml"
175
175
  - ".gitattributes"
176
+ - ".github/workflows/ci.yml"
176
177
  - ".gitignore"
177
178
  - ".rspec"
178
179
  - ".rubocop.yml"
179
- - ".travis.yml"
180
180
  - CHANGELOG.md
181
181
  - CODE_OF_CONDUCT.md
182
182
  - Gemfile
@@ -227,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
227
  - !ruby/object:Gem::Version
228
228
  version: '0'
229
229
  requirements: []
230
- rubygems_version: 3.5.20
230
+ rubygems_version: 3.5.21
231
231
  signing_key:
232
232
  specification_version: 4
233
233
  summary: Ruby Command based framework (aka service objects)
data/.travis.yml DELETED
@@ -1,25 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.5
6
- - 2.6
7
- - 2.7
8
- - ruby-head
9
- matrix:
10
- fast_finish: true
11
- allow_failures:
12
- - rvm: ruby-head
13
- before_install:
14
- - gem update --system
15
- - gem install bundler
16
- install:
17
- - bundle install --jobs=3 --retry=3
18
- script:
19
- - bundle exec rspec
20
- - bundle exec rubocop
21
- - bundle exec fasterer
22
- notifications:
23
- email: false
24
- slack:
25
- secure: 2PptrBf+yl8evd/WA4XKg0tzJqSdEn5TaAhuHR90o5X6Rd0MA4SES+QZQn9A81CPuJk//V8Dur8NQQzrcAivVOsmC+JbIHZV1MS8v32Uvoy28Vr8omAne4Ec3so6rXCIMniREGcHtKeXHdNYO61VZPPjgxd7RTwajvTC7KS0ViuKBPct+hq+WfJmX6Xp2vxTHImNVurExBrFZkJyPtV+UG1tJ6mB+kF8MQ1+t3ZBtU4+6eVviADYi7FBEeP0j9gdKBCzU7aPoE+XLMWcP0tI9ibaP9crEMKi05JNDLzm5f6PGvGJWEQSCeh3CxNhaEF7eog3BAw6mUXkKI0hvFfrZeyG+VkDU6T9rAG8u55BrREi4mRNJl0c0rS2J45bFRmYOlvDNn9xrhMkkfFn8SMX3etYJYqHNBxdF2TX4SzpDrshd3hcm2btmkThn/Ua/mYUWs9OF3O/ZSu0zB+/g83zrAsjMyfqJ1PE5ZiiMD2Rjrzb/fyYcsNeVhncwRdse9LtJRqz1KpOh/r5LBrWfRRCQwlVNwMtBdFxQ7Ytl9dz/dfJmGKgM8zsdJdI6ZyV+lwa0jPaql6Y8wXjox1wwN3IRCyvoVsgxmiQAV8jnWD2lf6I/Ea5yXCImGluuRAyRrAizElXGBNubhAVtQwQinHWfaYO+mJElScBwrxTVXuzQUI=