lite-command 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.5.0'
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.5.0
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: 2022-04-20 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.3.11
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,52 +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
- instance = new(*args, **kwargs, &block)
11
-
12
- raise Lite::Command::NotImplementedError unless instance.respond_to?(:execute)
13
-
14
- instance.call
15
- instance
16
- end
17
-
18
- def execute(*args, **kwargs, &block)
19
- instance = call(*args, **kwargs, &block)
20
- instance.result
21
- end
22
-
23
- end
24
-
25
- attr_reader :args, :result
26
-
27
- def initialize(*args)
28
- @args = args
29
- end
30
-
31
- def call
32
- raise Lite::Command::NotImplementedError unless defined?(execute)
33
-
34
- return @result if called?
35
-
36
- @called = true
37
- @result = execute
38
- end
39
-
40
- def called?
41
- @called ||= false
42
- end
43
-
44
- def recall!
45
- @called = false
46
- %i[cache errors].each { |method_name| send(method_name).clear if respond_to?(method_name) }
47
- call
48
- end
49
-
50
- end
51
- end
52
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Command
5
-
6
- class NotImplementedError < StandardError; end
7
- class ValidationError < StandardError; end
8
-
9
- end
10
- 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
- instance = call(*args, **kwargs, &block)
14
-
15
- if instance.success?
16
- yield(instance.result, Lite::Command::Success, Lite::Command::Failure)
17
- else
18
- yield(instance.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!(instance, direction: :from)
49
- case direction
50
- when :from then errors.merge!(instance.errors)
51
- when :to then instance.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