flow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/flow.svg)](https://badge.fury.io/rb/flow)
|
4
|
+
[![Build Status](https://semaphoreci.com/api/v1/freshly/flow/branches/master/badge.svg)](https://semaphoreci.com/freshly/flow)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/02131658005b10c289e0/maintainability)](https://codeclimate.com/github/Freshly/flow/maintainability)
|
6
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/02131658005b10c289e0/test_coverage)](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:
|