flow 0.1.0 → 0.2.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 +4 -4
- data/README.md +5 -0
- data/lib/flow.rb +15 -3
- data/lib/flow/flow_base.rb +38 -0
- data/lib/flow/operation/core.rb +16 -0
- data/lib/flow/operation/execute.rb +18 -0
- data/lib/flow/operation_base.rb +11 -0
- data/lib/flow/state/arguments.rb +30 -0
- data/lib/flow/state/attributes.rb +31 -0
- data/lib/flow/state/callbacks.rb +13 -0
- data/lib/flow/state/core.rb +14 -0
- data/lib/flow/state/options.rb +43 -0
- data/lib/flow/state/string.rb +38 -0
- data/lib/flow/state_base.rb +19 -0
- data/lib/flow/version.rb +3 -1
- metadata +84 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46f3b0a498f717414773a7c8262c4a045a229007c4066327929c41112ced44d0
|
4
|
+
data.tar.gz: 354e5069d30b3fc634c54d3c0170b7b2e4f84682cd2c9f52e84e47651bd1414b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d13a497617cf8aa949ca0c77d45d27613321a9417fde96f3f3a8c4a9874954243c29e7766bf0ca94bf3908e70238920e15a9eb4fe739012cfbe74a3e6eaad78
|
7
|
+
data.tar.gz: cd3a356d664d7b4b334cdc436cd03f382c48d57b81e9dc3664990d502a64e39d7fd15b9c2804a88dca9a0d3066ea7d593a3b22272886d190c283ccc5fabe4061
|
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Flow
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/flow)
|
4
|
+
[](https://semaphoreci.com/freshly/flow)
|
5
|
+
[](https://codeclimate.com/github/Freshly/flow/maintainability)
|
6
|
+
[](https://codeclimate.com/github/Freshly/flow/test_coverage)
|
7
|
+
|
3
8
|
Business logic is like nuclear fuel.
|
4
9
|
|
5
10
|
Managed properly, it's incredibly powerful and can do a lot of good.
|
data/lib/flow.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
|
5
|
+
require "active_support"
|
6
|
+
require "active_support/core_ext/class/attribute"
|
7
|
+
require "active_support/core_ext/module/delegation"
|
8
|
+
|
9
|
+
require "technologic"
|
10
|
+
|
1
11
|
require "flow/version"
|
2
12
|
|
3
|
-
|
4
|
-
|
5
|
-
|
13
|
+
require "flow/flow_base"
|
14
|
+
require "flow/operation_base"
|
15
|
+
require "flow/state_base"
|
16
|
+
|
17
|
+
module Flow; end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A flow is a collection of procedurally executed operations sharing a common state.
|
4
|
+
class FlowBase
|
5
|
+
class_attribute :_operations, instance_writer: false, default: []
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def state_class
|
9
|
+
"#{name.chomp("Flow")}State".constantize
|
10
|
+
end
|
11
|
+
|
12
|
+
def trigger(*arguments)
|
13
|
+
new(*arguments).trigger
|
14
|
+
end
|
15
|
+
|
16
|
+
def operations(*operations)
|
17
|
+
_operations.concat(operations.flatten)
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited(base)
|
21
|
+
base._operations = _operations.dup
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :state
|
27
|
+
|
28
|
+
delegate :state_class, :_operations, to: :class
|
29
|
+
|
30
|
+
def initialize(**input)
|
31
|
+
@state = state_class.new(**input)
|
32
|
+
end
|
33
|
+
|
34
|
+
def trigger
|
35
|
+
_operations.each { |operation| operation.execute(state) }
|
36
|
+
state
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Accepts input representing the state.
|
4
|
+
module Operation
|
5
|
+
module Core
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_reader :state
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(state)
|
13
|
+
@state = state
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Execute the operation.
|
4
|
+
module Operation
|
5
|
+
module Execute
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def execute(*arguments)
|
10
|
+
new(*arguments).execute
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "operation/core"
|
4
|
+
require_relative "operation/execute"
|
5
|
+
|
6
|
+
# Operations are service objects which are executed with a state.
|
7
|
+
class OperationBase
|
8
|
+
include Technologic
|
9
|
+
include Operation::Core
|
10
|
+
include Operation::Execute
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Arguments describe input required to define the initial state.
|
4
|
+
module State
|
5
|
+
module Arguments
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_arguments, instance_writer: false, default: []
|
10
|
+
set_callback :initialize, :after do
|
11
|
+
missing = _arguments.select { |argument| public_send(argument).nil? }
|
12
|
+
raise ArgumentError, "Missing #{"argument".pluralize(missing.length)}: #{missing.join(", ")}" if missing.any?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def inherited(base)
|
18
|
+
base._arguments = _arguments.dup
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def argument(argument)
|
25
|
+
_arguments << argument
|
26
|
+
define_attribute argument
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Defines the immutable structure by defining attribute accessors for the state data.
|
4
|
+
module State
|
5
|
+
module Attributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
|
11
|
+
class_attribute :_attributes, instance_writer: false, default: []
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def inherited(base)
|
16
|
+
base._attributes = _attributes.dup
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def define_attribute(attribute)
|
23
|
+
_attributes << attribute
|
24
|
+
|
25
|
+
attr_accessor attribute
|
26
|
+
define_attribute_methods attribute
|
27
|
+
protected "#{attribute}=".to_sym
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# State callbacks provide an extensible mechanism for composing functionality for a state object.
|
4
|
+
module State
|
5
|
+
module Callbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveSupport::Callbacks
|
10
|
+
define_callbacks :initialize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Accepts input representing the arguments and input which define the initial state.
|
4
|
+
module State
|
5
|
+
module Core
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize(**input)
|
9
|
+
run_callbacks(:initialize) do
|
10
|
+
input.each { |key, value| __send__("#{key}=".to_sym, value) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Options describe input which may be provided to define or override the initial state.
|
4
|
+
module State
|
5
|
+
module Options
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_options, instance_writer: false, default: {}
|
10
|
+
|
11
|
+
set_callback :initialize, :after do
|
12
|
+
_options.each { |option, info| __send__("#{option}=".to_sym, info.default_value) if public_send(option).nil? }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def inherited(base)
|
18
|
+
dup = _options.dup
|
19
|
+
base._options = dup.each { |k, v| dup[k] = v.dup }
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def option(option, default: nil, &block)
|
26
|
+
_options[option] = Option.new(default: default, &block)
|
27
|
+
define_attribute option
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Option
|
32
|
+
def initialize(default:, &block)
|
33
|
+
@default_value = (default.nil? && block_given?) ? block : default
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_value
|
37
|
+
return instance_eval(&@default_value) if @default_value.respond_to?(:call)
|
38
|
+
|
39
|
+
@default_value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Formats the state as a string.
|
4
|
+
module State
|
5
|
+
module String
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
delegate :name, to: :class, prefix: true
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
string_for(__method__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
string_for(__method__)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def stringable_attributes
|
21
|
+
self.class._attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def string_for(method)
|
27
|
+
"#<#{class_name} #{attribute_string(method)}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def attribute_string(method)
|
31
|
+
stringable_attribute_values.map { |attribute, value| "#{attribute}=#{value.public_send(method)}" }.join(" ")
|
32
|
+
end
|
33
|
+
|
34
|
+
def stringable_attribute_values
|
35
|
+
stringable_attributes.each_with_object({}) { |attribute, result| result[attribute] = public_send(attribute) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "state/callbacks"
|
4
|
+
require_relative "state/attributes"
|
5
|
+
require_relative "state/arguments"
|
6
|
+
require_relative "state/options"
|
7
|
+
require_relative "state/core"
|
8
|
+
require_relative "state/string"
|
9
|
+
|
10
|
+
# A flow state is the immutable structure of relevant data.
|
11
|
+
class StateBase
|
12
|
+
include ActiveModel::Model
|
13
|
+
include State::Callbacks
|
14
|
+
include State::Attributes
|
15
|
+
include State::Arguments
|
16
|
+
include State::Options
|
17
|
+
include State::Core
|
18
|
+
include State::String
|
19
|
+
end
|
data/lib/flow/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Garside
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: spicerack
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.4.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.4.3
|
13
55
|
- !ruby/object:Gem::Dependency
|
14
56
|
name: bundler
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,8 +164,36 @@ dependencies:
|
|
122
164
|
- - ">="
|
123
165
|
- !ruby/object:Gem::Version
|
124
166
|
version: 0.11.3
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspice
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.4.3
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 0.4.3
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: shoulda-matchers
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 4.0.0.rc1
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 4.0.0.rc1
|
125
195
|
description: Tired of kitchen sink services, god-objects, and fat-everything? So were
|
126
|
-
we. Get flow.
|
196
|
+
we. Get in the flow.
|
127
197
|
email:
|
128
198
|
- eric.garside@freshly.com
|
129
199
|
executables: []
|
@@ -133,6 +203,17 @@ files:
|
|
133
203
|
- LICENSE.txt
|
134
204
|
- README.md
|
135
205
|
- lib/flow.rb
|
206
|
+
- lib/flow/flow_base.rb
|
207
|
+
- lib/flow/operation/core.rb
|
208
|
+
- lib/flow/operation/execute.rb
|
209
|
+
- lib/flow/operation_base.rb
|
210
|
+
- lib/flow/state/arguments.rb
|
211
|
+
- lib/flow/state/attributes.rb
|
212
|
+
- lib/flow/state/callbacks.rb
|
213
|
+
- lib/flow/state/core.rb
|
214
|
+
- lib/flow/state/options.rb
|
215
|
+
- lib/flow/state/string.rb
|
216
|
+
- lib/flow/state_base.rb
|
136
217
|
- lib/flow/version.rb
|
137
218
|
homepage: http://www.freshly.com
|
138
219
|
licenses:
|