operational 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/AI_README.md +328 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE +21 -0
- data/README.md +668 -0
- data/lib/operational/controller.rb +26 -0
- data/lib/operational/error.rb +7 -0
- data/lib/operational/form.rb +72 -0
- data/lib/operational/operation/contract.rb +42 -0
- data/lib/operational/operation/nested.rb +13 -0
- data/lib/operational/operation.rb +66 -0
- data/lib/operational/result.rb +29 -0
- data/lib/operational/version.rb +3 -0
- data/lib/operational.rb +14 -0
- metadata +115 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Operational
|
|
2
|
+
class Form
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
include ActiveModel::Attributes
|
|
5
|
+
include ActiveModel::Dirty
|
|
6
|
+
|
|
7
|
+
def self.inherited(subclass)
|
|
8
|
+
super
|
|
9
|
+
subclass.instance_variable_set(:@_operational_sync_check_pending, true)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.method_added(method_name)
|
|
13
|
+
super
|
|
14
|
+
if method_name == :sync && @_operational_sync_check_pending
|
|
15
|
+
raise MethodCollision,
|
|
16
|
+
"#{self} defines #sync, which collides with Operational::Form#sync. " \
|
|
17
|
+
"Rename your method to #on_sync — it will be called automatically during sync."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.build(model: nil, model_persisted: nil, state: {}, build_method: :on_build)
|
|
22
|
+
form = new
|
|
23
|
+
|
|
24
|
+
if model
|
|
25
|
+
raise InvalidContractModel unless model.respond_to?(:attributes)
|
|
26
|
+
valid_form_attrs = form.attribute_names
|
|
27
|
+
valid_params = model.attributes.slice(*valid_form_attrs).compact
|
|
28
|
+
form.assign_attributes(valid_params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
form.instance_variable_set(:@_operational_model_persisted, (model_persisted.nil? ? model&.persisted? || false : !!model_persisted))
|
|
32
|
+
form.instance_variable_set(form.send(:_operational_state_variable), state.dup.freeze)
|
|
33
|
+
form.send(build_method, state) if form.respond_to?(build_method)
|
|
34
|
+
form.changes_applied if form.respond_to?(:changes_applied)
|
|
35
|
+
form
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate(params = {})
|
|
39
|
+
params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
|
|
40
|
+
params = params.with_indifferent_access if params.respond_to?(:with_indifferent_access)
|
|
41
|
+
valid_params = params.slice(*attribute_names)
|
|
42
|
+
assign_attributes(valid_params)
|
|
43
|
+
valid?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def sync(model: nil, state: {}, sync_method: :on_sync)
|
|
47
|
+
if model
|
|
48
|
+
raise InvalidContractModel unless model.respond_to?(:attributes)
|
|
49
|
+
valid_model_attrs = model.attribute_names
|
|
50
|
+
valid_params = attributes.slice(*valid_model_attrs)
|
|
51
|
+
model.assign_attributes(valid_params)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
send(sync_method, state) if respond_to?(sync_method)
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def persisted?
|
|
59
|
+
@_operational_model_persisted
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def other_validators_have_passed?
|
|
63
|
+
errors.blank?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
protected
|
|
67
|
+
|
|
68
|
+
def _operational_state_variable
|
|
69
|
+
:@state
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Operational
|
|
2
|
+
class Operation
|
|
3
|
+
module Contract
|
|
4
|
+
def self.Build(contract:, name: :contract, model_key: :model, model_persisted: nil, build_method: :on_build)
|
|
5
|
+
lambda do |state|
|
|
6
|
+
model = state.key?(model_key) ? state[model_key] : nil
|
|
7
|
+
raise InvalidContractModel if state.key?(model_key) && model.nil?
|
|
8
|
+
|
|
9
|
+
state[name] = contract.build(
|
|
10
|
+
model: model,
|
|
11
|
+
model_persisted: model_persisted,
|
|
12
|
+
state: state,
|
|
13
|
+
build_method: build_method
|
|
14
|
+
)
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.Validate(name: :contract, params_path: nil)
|
|
20
|
+
lambda do |state|
|
|
21
|
+
valid_path = case
|
|
22
|
+
when params_path.nil? then [:params]
|
|
23
|
+
when params_path.is_a?(Symbol) then [:params, params_path]
|
|
24
|
+
when params_path.is_a?(Array) then params_path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
raw_params = state.dig(*valid_path) || {}
|
|
28
|
+
state[name].validate(raw_params)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.Sync(name: :contract, model_key: :model, sync_method: :on_sync)
|
|
33
|
+
lambda do |state|
|
|
34
|
+
model = state.key?(model_key) ? state[model_key] : nil
|
|
35
|
+
raise InvalidContractModel if state.key?(model_key) && model.nil?
|
|
36
|
+
|
|
37
|
+
state[name].sync(model: model, state: state, sync_method: sync_method)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Operational
|
|
2
|
+
class Operation
|
|
3
|
+
def self.step(action)
|
|
4
|
+
add_step(:step, action)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.pass(action)
|
|
8
|
+
add_step(:pass, action)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.fail(action)
|
|
12
|
+
add_step(:fail, action)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.call(state={})
|
|
16
|
+
instance = self.new
|
|
17
|
+
instance.instance_variable_set(:@_operational_state, state)
|
|
18
|
+
instance.instance_variable_set(:@_operational_path, [])
|
|
19
|
+
instance.instance_variable_set(:@_operational_succeeded, true)
|
|
20
|
+
|
|
21
|
+
failure_circuit = false
|
|
22
|
+
|
|
23
|
+
instance.send(:_operational_steps).each do |railway_step|
|
|
24
|
+
type, action = railway_step
|
|
25
|
+
|
|
26
|
+
next if !failure_circuit && type == :fail
|
|
27
|
+
next if failure_circuit && type != :fail
|
|
28
|
+
|
|
29
|
+
result = if action.is_a?(Symbol)
|
|
30
|
+
instance.send(action, instance.send(:_operational_state))
|
|
31
|
+
elsif action.respond_to?(:call)
|
|
32
|
+
action.call(instance.send(:_operational_state))
|
|
33
|
+
else
|
|
34
|
+
raise UnknownStepType
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result = true if type == :pass
|
|
38
|
+
instance.instance_variable_set(:@_operational_succeeded, result ? true : false)
|
|
39
|
+
instance.instance_variable_get(:@_operational_path) << (result ? true : false)
|
|
40
|
+
|
|
41
|
+
failure_circuit = !instance.instance_variable_get(:@_operational_succeeded) && type != :pass
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return Result.new(
|
|
45
|
+
succeeded: (instance.instance_variable_get(:@_operational_succeeded) ? true : false),
|
|
46
|
+
state: instance.instance_variable_get(:@_operational_state),
|
|
47
|
+
operation: instance)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def self.add_step(type, action)
|
|
53
|
+
railway = class_variable_defined?(:@@_operational_steps) ? class_variable_get(:@@_operational_steps) : []
|
|
54
|
+
railway << [type, action]
|
|
55
|
+
class_variable_set(:@@_operational_steps, railway)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def _operational_steps
|
|
59
|
+
self.class.class_variable_defined?(:@@_operational_steps) ? self.class.class_variable_get(:@@_operational_steps) : []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def _operational_state
|
|
63
|
+
@_operational_state
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Operational
|
|
2
|
+
class Result
|
|
3
|
+
def initialize(succeeded:, state:, operation:)
|
|
4
|
+
@succeeded = succeeded
|
|
5
|
+
@state = state.dup.freeze
|
|
6
|
+
@operation = operation
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def succeeded?
|
|
10
|
+
@succeeded
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def failed?
|
|
14
|
+
!@succeeded
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def operation
|
|
18
|
+
@operation
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def state
|
|
22
|
+
@state
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def [](i)
|
|
26
|
+
@state[i]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/operational.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'active_model'
|
|
2
|
+
|
|
3
|
+
require "operational/version"
|
|
4
|
+
require "operational/error"
|
|
5
|
+
require "operational/operation"
|
|
6
|
+
require "operational/result"
|
|
7
|
+
require "operational/form"
|
|
8
|
+
require "operational/controller"
|
|
9
|
+
|
|
10
|
+
require "operational/operation/contract"
|
|
11
|
+
require "operational/operation/nested"
|
|
12
|
+
|
|
13
|
+
module Operational
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: operational
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bryan Rite
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activemodel
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 7.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 7.0.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bundler
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: Operational wraps your business logic into operations — small classes
|
|
69
|
+
with a railway of steps that succeed or fail. Pair them with form objects and contracts
|
|
70
|
+
to decouple your UI and APIs from your models.
|
|
71
|
+
email:
|
|
72
|
+
- bryan@bryanrite.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- AI_README.md
|
|
78
|
+
- CHANGELOG.md
|
|
79
|
+
- LICENSE
|
|
80
|
+
- README.md
|
|
81
|
+
- lib/operational.rb
|
|
82
|
+
- lib/operational/controller.rb
|
|
83
|
+
- lib/operational/error.rb
|
|
84
|
+
- lib/operational/form.rb
|
|
85
|
+
- lib/operational/operation.rb
|
|
86
|
+
- lib/operational/operation/contract.rb
|
|
87
|
+
- lib/operational/operation/nested.rb
|
|
88
|
+
- lib/operational/result.rb
|
|
89
|
+
- lib/operational/version.rb
|
|
90
|
+
homepage: https://github.com/bryanrite/operational
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata:
|
|
94
|
+
changelog_uri: https://github.com/bryanrite/operational/blob/master/CHANGELOG.md
|
|
95
|
+
source_code_uri: https://github.com/bryanrite/operational
|
|
96
|
+
documentation_uri: https://github.com/bryanrite/operational#readme
|
|
97
|
+
bug_tracker_uri: https://github.com/bryanrite/operational/issues
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '3.0'
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
requirements: []
|
|
112
|
+
rubygems_version: 3.6.9
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: Lightweight, railway-oriented operation and form objects for business logic
|
|
115
|
+
test_files: []
|