lite-command 1.4.1 → 2.0.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.
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Command
5
+
6
+ # Status represents the state of the callable code. If no fault
7
+ # is thrown then a status of SUCCESS is returned even if `call`
8
+ # has not been executed.
9
+ FAULTS = [
10
+ NOOP = "noop",
11
+ INVALID = "invalid",
12
+ FAILURE = "failure",
13
+ ERROR = "error"
14
+ ].freeze
15
+ STATUSES = [
16
+ *FAULTS,
17
+ SUCCESS = "success"
18
+ ].freeze
19
+
20
+ module Internals
21
+ module Callable
22
+
23
+ def self.included(base)
24
+ base.extend ClassMethods
25
+ base.class_eval do
26
+ attr_reader :faulter, :thrower, :reason
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ def call(context = {})
33
+ new(context).tap(&:execute)
34
+ end
35
+
36
+ def call!(context = {})
37
+ new(context).tap(&:execute!)
38
+ end
39
+
40
+ end
41
+
42
+ def call
43
+ raise NotImplementedError, "call method not defined in #{self.class}"
44
+ end
45
+
46
+ def success?
47
+ !fault?
48
+ end
49
+
50
+ def fault?(message = nil)
51
+ FAULTS.any? { |f| send(:"#{f}?", message) }
52
+ end
53
+
54
+ def status
55
+ STATUSES.find { |s| send(:"#{s}?") }
56
+ end
57
+
58
+ def faulter?
59
+ faulter == self
60
+ end
61
+
62
+ def thrower?
63
+ thrower == self
64
+ end
65
+
66
+ def thrown_fault?
67
+ fault? && !faulter?
68
+ end
69
+
70
+ FAULTS.each do |f|
71
+ # eg: error?(message = nil)
72
+ define_method(:"#{f}?") do |message = nil|
73
+ fault_result = instance_variable_get(:"@#{f}") || false
74
+ return fault_result if message.nil?
75
+
76
+ reason == message
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def derive_faulter_from(object)
83
+ (object.faulter if object.respond_to?(:faulter)) || self
84
+ end
85
+
86
+ def derive_thrower_from(object)
87
+ if object.respond_to?(:executed?) && object.executed?
88
+ object
89
+ else
90
+ (object.thrower if object.respond_to?(:thrower)) || faulter
91
+ end
92
+ end
93
+
94
+ def derive_reason_from(object)
95
+ if object.respond_to?(:reason)
96
+ object.reason
97
+ elsif object.respond_to?(:message)
98
+ "[#{object.class.name}] #{object.message}".chomp(".")
99
+ else
100
+ object
101
+ end
102
+ end
103
+
104
+ def fault(object)
105
+ @faulter ||= derive_faulter_from(object)
106
+ @thrower ||= derive_thrower_from(object)
107
+ @reason ||= derive_reason_from(object)
108
+ end
109
+
110
+ # eg: Lite::Command::Noop.new(...)
111
+ def raise_fault(klass, object)
112
+ exception = klass.new(faulter, self, reason)
113
+ exception.set_backtrace(object.backtrace) if object.respond_to?(:backtrace)
114
+ raise(exception)
115
+ end
116
+
117
+ # eg: Users::ResetPassword::Noop.new(...)
118
+ def raise_dynamic_fault(exception)
119
+ fault_klass = self.class.const_get(exception.fault_klass)
120
+ raise_fault(fault_klass, exception)
121
+ end
122
+
123
+ def raise_dynamic_faults?
124
+ false
125
+ end
126
+
127
+ def throw!(command)
128
+ return if command.success?
129
+
130
+ send(:"#{command.status}!", command)
131
+ end
132
+
133
+ FAULTS.each do |f|
134
+ # eg: error(object)
135
+ define_method(:"#{f}") do |object|
136
+ fault(object)
137
+ instance_variable_set(:"@#{f}", true)
138
+ end
139
+
140
+ # eg: invalid!(object)
141
+ define_method(:"#{f}!") do |object|
142
+ send(:"#{f}", object)
143
+ raise_fault(Lite::Command.const_get(f.capitalize), object)
144
+ end
145
+
146
+ # eg: on_noop(exception)
147
+ define_method(:"on_#{f}") do |_exception|
148
+ # Define in your class to run code when a StandardError happens
149
+ end
150
+ end
151
+
152
+ alias fail! failure!
153
+
154
+ end
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Command
5
+
6
+ # State represents the state of the executable code. Once `execute`
7
+ # is ran, it will always complete or dnf if a fault is thrown by a
8
+ # child command.
9
+ STATES = [
10
+ PENDING = "pending",
11
+ EXECUTING = "executing",
12
+ COMPLETE = "complete",
13
+ DNF = "dnf"
14
+ ].freeze
15
+
16
+ module Internals
17
+ module Executable
18
+
19
+ def execute
20
+ around_execution { call }
21
+ rescue StandardError => e
22
+ fn = e.respond_to?(:fault_name) ? e.fault_name : ERROR
23
+
24
+ send(:"#{fn}", e)
25
+ after_execution
26
+ send(:"on_#{fn}", e)
27
+ end
28
+
29
+ def execute!
30
+ around_execution { call }
31
+ rescue StandardError => e
32
+ after_execution
33
+
34
+ raise(e) unless raise_dynamic_faults? && e.is_a?(Lite::Command::Fault)
35
+
36
+ raise_dynamic_fault(e)
37
+ end
38
+
39
+ def state
40
+ @state || PENDING
41
+ end
42
+
43
+ def executed?
44
+ dnf? || complete?
45
+ end
46
+
47
+ STATES.each do |s|
48
+ # eg: running?
49
+ define_method(:"#{s}?") { state == s }
50
+
51
+ # eg: dnf!
52
+ define_method(:"#{s}!") { @state = s }
53
+ end
54
+
55
+ private
56
+
57
+ def before_execution
58
+ increment_execution_index
59
+ assign_execution_cid
60
+ start_monotonic_time
61
+ executing!
62
+ on_before_execution
63
+ end
64
+
65
+ def after_execution
66
+ fault? ? dnf! : complete!
67
+ on_after_execution
68
+ stop_monotonic_time
69
+ append_execution_result
70
+ freeze_execution_objects
71
+ end
72
+
73
+ def around_execution
74
+ before_execution
75
+ yield
76
+ after_execution
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom" unless defined?(SecureRandom)
4
+
5
+ module Lite
6
+ module Command
7
+ module Internals
8
+ module Resultable
9
+
10
+ def index
11
+ @index ||= context.index ||= 0
12
+ end
13
+
14
+ def cid
15
+ @cid ||= context.cid
16
+ end
17
+
18
+ def outcome
19
+ return state if pending? || thrown_fault?
20
+
21
+ status
22
+ end
23
+
24
+ def results
25
+ @results ||= context.results ||= []
26
+ end
27
+
28
+ def to_hash
29
+ {
30
+ index:,
31
+ cid:,
32
+ command: self.class.name,
33
+ outcome:,
34
+ state:,
35
+ status:,
36
+ reason:,
37
+ fault: faulter&.index,
38
+ throw: thrower&.index,
39
+ runtime:
40
+ }.compact
41
+ end
42
+ alias to_h to_hash
43
+
44
+ private
45
+
46
+ def assign_execution_cid
47
+ context.cid ||= SecureRandom.uuid
48
+ end
49
+
50
+ def increment_execution_index
51
+ @index = context.index = index.next
52
+ end
53
+
54
+ def start_monotonic_time
55
+ @start_monotonic_time ||= Process.clock_gettime(Process::CLOCK_MONOTONIC)
56
+ end
57
+
58
+ def stop_monotonic_time
59
+ @stop_monotonic_time ||= Process.clock_gettime(Process::CLOCK_MONOTONIC)
60
+ end
61
+
62
+ def runtime
63
+ stop_monotonic_time - start_monotonic_time
64
+ end
65
+
66
+ def append_execution_result
67
+ results.push(self).sort_by!(&:index)
68
+ end
69
+
70
+ def freeze_execution_objects
71
+ context.freeze if index == 1
72
+ freeze
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ end
@@ -3,7 +3,7 @@
3
3
  module Lite
4
4
  module Command
5
5
 
6
- VERSION = '1.4.1'
6
+ VERSION = "2.0.0"
7
7
 
8
8
  end
9
9
  end
data/lib/lite/command.rb CHANGED
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'generators/rails/command_generator' if defined?(Rails::Generators)
4
-
5
- require 'lite/command/version'
6
-
7
- require 'lite/command/extensions/errors'
8
- require 'lite/command/extensions/memoize'
9
- require 'lite/command/extensions/propagation'
10
-
11
- require 'lite/command/exceptions'
12
- require 'lite/command/states'
13
- require 'lite/command/simple'
14
- require 'lite/command/complex'
15
- require 'lite/command/procedure'
3
+ require "generators/rails/command_generator" if defined?(Rails::Generators)
4
+
5
+ require "lite/command/version"
6
+ require "lite/command/internals/callable"
7
+ require "lite/command/internals/executable"
8
+ require "lite/command/internals/resultable"
9
+ require "lite/command/fault"
10
+ require "lite/command/context"
11
+ require "lite/command/base"
data/lite-command.gemspec CHANGED
@@ -1,55 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path("../lib", __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'lite/command/version'
5
+ require "lite/command/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'lite-command'
8
+ spec.name = "lite-command"
9
9
  spec.version = Lite::Command::VERSION
10
- spec.authors = ['Juan Gomez']
10
+ spec.authors = ["Juan Gomez"]
11
11
  spec.email = %w[j.gomez@drexed.com]
12
12
 
13
- spec.summary = 'Ruby Command based framework (aka service objects)'
14
- spec.homepage = 'http://drexed.github.io/lite-command'
15
- spec.license = 'MIT'
13
+ spec.summary = "Ruby Command based framework (aka service objects)"
14
+ spec.homepage = "http://drexed.github.io/lite-command"
15
+ spec.license = "MIT"
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
19
19
  if spec.respond_to?(:metadata)
20
20
  spec.metadata.merge(
21
- 'allowed_push_host' => 'https://rubygems.org',
22
- 'changelog_uri' => 'https://github.com/drexed/lite-command/blob/master/CHANGELOG.md',
23
- 'homepage_uri' => spec.homepage,
24
- 'source_code_uri' => 'https://github.com/drexed/lite-command'
21
+ "allowed_push_host" => "https://rubygems.org",
22
+ "changelog_uri" => "https://github.com/drexed/lite-command/blob/master/CHANGELOG.md",
23
+ "homepage_uri" => spec.homepage,
24
+ "source_code_uri" => "https://github.com/drexed/lite-command"
25
25
  )
26
26
  else
27
- raise 'RubyGems 2.0 or newer is required to protect against ' \
28
- 'public gem pushes.'
27
+ raise "RubyGems 2.0 or newer is required to protect against " \
28
+ "public gem pushes."
29
29
  end
30
30
 
31
31
  # Specify which files should be added to the gem when it is released.
32
32
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
33
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
34
34
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
35
35
  end
36
- spec.bindir = 'exe'
36
+ spec.bindir = "exe"
37
37
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
38
  spec.require_paths = %w[lib]
39
39
 
40
- spec.add_dependency 'lite-errors'
41
- spec.add_dependency 'lite-memoize'
40
+ spec.add_dependency "activemodel"
41
+ spec.add_dependency "ostruct"
42
42
 
43
- spec.add_development_dependency 'activerecord'
44
- spec.add_development_dependency 'bundler'
45
- spec.add_development_dependency 'database_cleaner'
46
- spec.add_development_dependency 'fasterer'
47
- spec.add_development_dependency 'generator_spec'
48
- spec.add_development_dependency 'rake'
49
- spec.add_development_dependency 'rspec'
50
- spec.add_development_dependency 'rubocop'
51
- spec.add_development_dependency 'rubocop-performance'
52
- spec.add_development_dependency 'rubocop-rake'
53
- spec.add_development_dependency 'rubocop-rspec'
54
- spec.add_development_dependency 'sqlite3'
43
+ spec.add_development_dependency "bundler"
44
+ spec.add_development_dependency "fasterer"
45
+ spec.add_development_dependency "generator_spec"
46
+ spec.add_development_dependency "rake"
47
+ spec.add_development_dependency "rspec"
48
+ spec.add_development_dependency "rubocop"
49
+ spec.add_development_dependency "rubocop-performance"
50
+ spec.add_development_dependency "rubocop-rake"
51
+ spec.add_development_dependency "rubocop-rspec"
52
+ spec.add_development_dependency "sqlite3"
53
+ spec.metadata["rubygems_mfa_required"] = "true"
55
54
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lite-command
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-04 00:00:00.000000000 Z
11
+ date: 2024-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: lite-errors
14
+ name: activemodel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: lite-memoize
28
+ name: ostruct
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: activerecord
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
41
  - !ruby/object:Gem::Dependency
56
42
  name: bundler
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +52,6 @@ dependencies:
66
52
  - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: database_cleaner
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
55
  - !ruby/object:Gem::Dependency
84
56
  name: fasterer
85
57
  requirement: !ruby/object:Gem::Requirement
@@ -233,20 +205,19 @@ files:
233
205
  - lib/generators/rails/command_generator.rb
234
206
  - lib/generators/rails/templates/command.rb.tt
235
207
  - lib/lite/command.rb
236
- - lib/lite/command/complex.rb
237
- - lib/lite/command/exceptions.rb
238
- - lib/lite/command/extensions/errors.rb
239
- - lib/lite/command/extensions/memoize.rb
240
- - lib/lite/command/extensions/propagation.rb
241
- - lib/lite/command/procedure.rb
242
- - lib/lite/command/simple.rb
243
- - lib/lite/command/states.rb
208
+ - lib/lite/command/base.rb
209
+ - lib/lite/command/context.rb
210
+ - lib/lite/command/fault.rb
211
+ - lib/lite/command/internals/callable.rb
212
+ - lib/lite/command/internals/executable.rb
213
+ - lib/lite/command/internals/resultable.rb
244
214
  - lib/lite/command/version.rb
245
215
  - lite-command.gemspec
246
216
  homepage: http://drexed.github.io/lite-command
247
217
  licenses:
248
218
  - MIT
249
- metadata: {}
219
+ metadata:
220
+ rubygems_mfa_required: 'true'
250
221
  post_install_message:
251
222
  rdoc_options: []
252
223
  require_paths:
@@ -262,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
262
233
  - !ruby/object:Gem::Version
263
234
  version: '0'
264
235
  requirements: []
265
- rubygems_version: 3.2.26
236
+ rubygems_version: 3.5.19
266
237
  signing_key:
267
238
  specification_version: 4
268
239
  summary: Ruby Command based framework (aka service objects)
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Command
5
- class Complex
6
-
7
- class << self
8
-
9
- def call(*args, **kwargs, &block)
10
- klass = new(*args, **kwargs, &block)
11
- raise Lite::Command::NotImplementedError unless klass.respond_to?(:execute)
12
-
13
- klass.call
14
- klass
15
- end
16
-
17
- def execute(*args, **kwargs, &block)
18
- klass = call(*args, **kwargs, &block)
19
- klass.result
20
- end
21
-
22
- end
23
-
24
- attr_reader :args, :result
25
-
26
- def initialize(*args)
27
- @args = args
28
- end
29
-
30
- def call
31
- raise Lite::Command::NotImplementedError unless defined?(execute)
32
- return @result if called?
33
-
34
- @called = true
35
- @result = execute
36
- end
37
-
38
- def called?
39
- @called ||= false
40
- end
41
-
42
- def recall!
43
- @called = false
44
- %i[cache errors].each { |mixin| send(mixin).clear if respond_to?(mixin) }
45
- call
46
- end
47
-
48
- end
49
- end
50
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Command
5
-
6
- class NotImplementedError < StandardError; end
7
-
8
- class ValidationError < StandardError; end
9
-
10
- end
11
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'lite/errors' unless defined?(Lite::Errors)
4
-
5
- module Lite
6
- module Command
7
- module Extensions
8
- module Errors
9
-
10
- module ClassMethods
11
-
12
- def perform(*args, **kwargs, &block)
13
- klass = call(*args, **kwargs, &block)
14
-
15
- if klass.success?
16
- yield(klass.result, Lite::Command::Success, Lite::Command::Failure)
17
- else
18
- yield(klass.result, Lite::Command::Failure, Lite::Command::Success)
19
- end
20
- end
21
-
22
- end
23
-
24
- class << self
25
-
26
- def included(klass)
27
- klass.extend(ClassMethods)
28
- end
29
-
30
- end
31
-
32
- def errors
33
- @errors ||= Lite::Errors::Messages.new
34
- end
35
-
36
- def errored?
37
- !errors.empty?
38
- end
39
-
40
- def fail!
41
- raise Lite::Command::ValidationError
42
- end
43
-
44
- def failure?
45
- called? && errored?
46
- end
47
-
48
- def merge_errors!(klass, direction: :from)
49
- case direction
50
- when :from then errors.merge!(klass.errors)
51
- when :to then klass.errors.merge!(errors)
52
- end
53
-
54
- nil
55
- end
56
-
57
- def merge_exception!(exception, key: :internal)
58
- errors.add(key, "#{exception.class} - #{exception.message}")
59
-
60
- nil
61
- end
62
-
63
- def result!
64
- result if valid?
65
- end
66
-
67
- def status
68
- return :pending unless called?
69
-
70
- success? ? :success : :failure
71
- end
72
-
73
- def success?
74
- called? && !errored?
75
- end
76
-
77
- def validate!
78
- return true if success?
79
-
80
- fail!
81
- end
82
-
83
- alias valid? validate!
84
-
85
- end
86
- end
87
- end
88
- end