trailblazer-operation 0.0.2
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/.travis.yml +11 -0
- data/CHANGES.md +16 -0
- data/Gemfile +13 -0
- data/README.md +3 -0
- data/Rakefile +19 -0
- data/lib/trailblazer/operation.rb +23 -0
- data/lib/trailblazer/operation/generic.rb +12 -0
- data/lib/trailblazer/operation/pipetree.rb +82 -0
- data/lib/trailblazer/operation/result.rb +27 -0
- data/lib/trailblazer/operation/skill.rb +31 -0
- data/lib/trailblazer/operation/stepable.rb +17 -0
- data/lib/trailblazer/operation/version.rb +7 -0
- data/lib/trailblazer/skill.rb +39 -0
- data/test/benchmark/skill_resolver_benchmark.rb +43 -0
- data/test/call_test.rb +28 -0
- data/test/dry_container_test.rb +20 -0
- data/test/gemfiles/Gemfile.ruby-1.9 +2 -0
- data/test/gemfiles/Gemfile.ruby-1.9.lock +29 -0
- data/test/gemfiles/Gemfile.ruby-2.3 +5 -0
- data/test/gemfiles/Gemfile.ruby-2.3.lock +40 -0
- data/test/inheritance_test.rb +28 -0
- data/test/operation_skill_test.rb +90 -0
- data/test/pipetree_test.rb +82 -0
- data/test/result_test.rb +24 -0
- data/test/skill_test.rb +61 -0
- data/test/test_helper.rb +2 -0
- data/trailblazer-operation.gemspec +30 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f87d954977f1411476bf2e2e5dfb93eb611fa285
|
4
|
+
data.tar.gz: 8faa3e771214751c0b5eca3c2546a24ef1ffe589
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c401fdbdb86bee4af234a1a9cbc3cc08c7062dda7bc264eb2d899d3b8ee1ac463d8a3a0f76a9a9504e21ad7f3d4268d7cc74dadf2196910fdd2022923328f598
|
7
|
+
data.tar.gz: 4faa6d2eef74f061c2125952b618ac6eda7aad652153e3550c0d645bcc42dab78b54b039fe898d9518c4aa27b6bcb1d910cc2b6586028d6b40ff7397a55e0ff2
|
data/.travis.yml
ADDED
data/CHANGES.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
## 0.0.2
|
2
|
+
|
3
|
+
* Works.
|
4
|
+
|
5
|
+
|
6
|
+
## TODO:
|
7
|
+
|
8
|
+
* test and assure it still works in ruby 1.9.
|
9
|
+
|
10
|
+
|
11
|
+
res = Op.()
|
12
|
+
res.auth { redirect_to "/login" }
|
13
|
+
res.double ( flash[:error] = "Record exist" }
|
14
|
+
res.success { redirect_to res.model.path }
|
15
|
+
|
16
|
+
endpoint: new layer for presentation/reaction
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in trailblazer.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "multi_json"
|
7
|
+
|
8
|
+
gem "dry-auto_inject"
|
9
|
+
gem "dry-matcher"
|
10
|
+
|
11
|
+
gem "minitest-line"
|
12
|
+
gem "benchmark-ips"
|
13
|
+
# gem "pipetree", path: "../pipetree"
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |test|
|
7
|
+
test.libs << 'test'
|
8
|
+
test.verbose = true
|
9
|
+
|
10
|
+
test_files = FileList['test/*_test.rb']
|
11
|
+
|
12
|
+
if RUBY_VERSION == "1.9.3"
|
13
|
+
test_files = test_files - %w{test/dry_container_test.rb}
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
puts test_files.inspect
|
18
|
+
test.test_files = test_files
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "declarative" # FIXME: here?
|
2
|
+
require "trailblazer/operation/skill"
|
3
|
+
require "trailblazer/operation/pipetree"
|
4
|
+
require "trailblazer/operation/generic"
|
5
|
+
require "trailblazer/operation/stepable"
|
6
|
+
|
7
|
+
module Trailblazer
|
8
|
+
# The Trailblazer-style operation.
|
9
|
+
# Note that you don't have to use our "opinionated" version with result object, skills, etc.
|
10
|
+
class Operation
|
11
|
+
extend Declarative::Heritage::Inherited
|
12
|
+
extend Declarative::Heritage::DSL
|
13
|
+
|
14
|
+
extend Skill::Accessors # ::[] and ::[]=
|
15
|
+
|
16
|
+
include Pipetree # ::call, ::|
|
17
|
+
# we want the skill dependency-mechanism.
|
18
|
+
extend Skill::Call # ::call
|
19
|
+
|
20
|
+
# we want the initializer and the ::call method.
|
21
|
+
include Generic # #initialize, #call, #process.
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "pipetree"
|
2
|
+
require "pipetree/flow"
|
3
|
+
require "trailblazer/operation/result"
|
4
|
+
require "uber/option"
|
5
|
+
|
6
|
+
class Trailblazer::Operation
|
7
|
+
New = ->(klass, options) { klass.new(options) } # returns operation instance.
|
8
|
+
Process = ->(operation, options) { operation.process(options["params"]) }
|
9
|
+
|
10
|
+
# http://trailblazer.to/gems/operation/2.0/pipetree.html
|
11
|
+
module Pipetree
|
12
|
+
def self.included(includer)
|
13
|
+
includer.extend ClassMethods # ::call, ::inititalize_pipetree!
|
14
|
+
includer.extend DSL # ::|, ::> and friends.
|
15
|
+
|
16
|
+
includer.initialize_pipetree!
|
17
|
+
includer.>> New, name: "operation.new"
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Top-level, this method is called when you do Create.() and where
|
22
|
+
# all the fun starts, ends, and hopefully starts again.
|
23
|
+
def call(options)
|
24
|
+
pipe = self["pipetree"] # TODO: injectable? WTF? how cool is that?
|
25
|
+
|
26
|
+
last, operation = pipe.(self, options) # operation == self, usually.
|
27
|
+
|
28
|
+
Result.new(last == ::Pipetree::Flow::Right, operation)
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method would be redundant if Ruby had a Class::finalize! method the way
|
32
|
+
# Dry.RB provides it. It has to be executed with every subclassing.
|
33
|
+
def initialize_pipetree!
|
34
|
+
heritage.record :initialize_pipetree!
|
35
|
+
self["pipetree"] = ::Pipetree::Flow[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module DSL
|
40
|
+
# They all inherit.
|
41
|
+
def >>(*args); _insert(:>>, *args) end
|
42
|
+
def >(*args); _insert(:>, *args) end
|
43
|
+
def &(*args); _insert(:&, *args) end
|
44
|
+
def <(*args); _insert(:<, *args) end
|
45
|
+
|
46
|
+
# :private:
|
47
|
+
def _insert(operator, proc, options={})
|
48
|
+
heritage.record(:_insert, operator, proc, options)
|
49
|
+
self["pipetree"].send(operator, Uber::Option[proc], options) # ex: pipetree.> Validate, after: Model::Build
|
50
|
+
end
|
51
|
+
|
52
|
+
def ~(cfg)
|
53
|
+
heritage.record(:~, cfg)
|
54
|
+
|
55
|
+
self.|(cfg, inheriting: true) # FIXME: not sure if this is the final API.
|
56
|
+
end
|
57
|
+
|
58
|
+
def |(cfg, user_options={}) # sorry for the magic here, but still playing with the DSL.
|
59
|
+
if cfg.is_a?(Stepable) # e.g. Contract::Validate
|
60
|
+
import = Import.new(self, user_options)
|
61
|
+
|
62
|
+
return cfg.import!(self, import) &&
|
63
|
+
heritage.record(:|, cfg, user_options)
|
64
|
+
end
|
65
|
+
|
66
|
+
self.> cfg, user_options # calls heritage.record
|
67
|
+
end
|
68
|
+
|
69
|
+
# Try to abstract as much as possible from the imported module. This is for
|
70
|
+
# forward-compatibility.
|
71
|
+
Import = Struct.new(:operation, :user_options) do
|
72
|
+
def call(operator, step, options)
|
73
|
+
operation["pipetree"].send operator, step, options.merge(user_options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def inheriting?
|
77
|
+
user_options[:inheriting]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
class Result
|
3
|
+
def initialize(success, data)
|
4
|
+
@success, @data = success, data
|
5
|
+
end
|
6
|
+
|
7
|
+
extend Uber::Delegates
|
8
|
+
delegates :@data, :[]
|
9
|
+
|
10
|
+
def success?
|
11
|
+
@success
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure?
|
15
|
+
! success?
|
16
|
+
end
|
17
|
+
|
18
|
+
# DISCUSS: the two methods below are more for testing.
|
19
|
+
def inspect
|
20
|
+
"<Result:#{success?} #{@data.inspect} >"
|
21
|
+
end
|
22
|
+
|
23
|
+
def slice(*keys)
|
24
|
+
keys.collect { |k| self[k] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "trailblazer/skill"
|
2
|
+
require "uber/delegates"
|
3
|
+
|
4
|
+
# Dependency ("skill") management for Operation.
|
5
|
+
# Op::[]
|
6
|
+
# Op::[]=
|
7
|
+
# Writing, even with an existing name, will never mutate a container.
|
8
|
+
# Op#[]
|
9
|
+
# Op#[]=
|
10
|
+
# Op.(params, { "constructor" => competences })
|
11
|
+
class Trailblazer::Operation
|
12
|
+
module Skill
|
13
|
+
# The class-level skill container: Operation::[], ::[]=.
|
14
|
+
module Accessors
|
15
|
+
# :private:
|
16
|
+
def skills
|
17
|
+
@skills ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
extend Uber::Delegates
|
21
|
+
delegates :skills, :[], :[]=
|
22
|
+
end
|
23
|
+
|
24
|
+
# Overrides Operation::call, creates the Skill hash and passes it to :call.
|
25
|
+
module Call
|
26
|
+
def call(params={}, options={}, *dependencies)
|
27
|
+
super Trailblazer::Skill.new(mutual={}, options.merge("params" => params), *dependencies, self.skills)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Stepable
|
3
|
+
Configuration = Struct.new(:mod, :args, :block) do
|
4
|
+
include Stepable # mark it, so that ::| thinks this is a step module.
|
5
|
+
|
6
|
+
def import!(operation, import)
|
7
|
+
mod.import!(operation, import, *args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](*args, &block)
|
12
|
+
# When called like Builder["builder.crud"], create a proxy
|
13
|
+
# object and Pipeline::| calls #import! on it.
|
14
|
+
Configuration.new(self, args, block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# TODO: mark/make all but mutable_options as frozen.
|
2
|
+
module Trailblazer
|
3
|
+
class Skill
|
4
|
+
def initialize(mutuable_options, *containers)
|
5
|
+
@mutuable_options = mutuable_options
|
6
|
+
@resolver = Resolver.new(@mutuable_options, *containers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
@resolver[name]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(name, value)
|
14
|
+
@mutuable_options[name] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
# Look through a list of containers until you find the skill.
|
18
|
+
class Resolver
|
19
|
+
# alternative implementation:
|
20
|
+
# containers.reverse.each do |container| @mutuable_options.merge!(container) end
|
21
|
+
#
|
22
|
+
# benchmark, merging in #initialize vs. this resolver.
|
23
|
+
# merge 39.678k (± 9.1%) i/s - 198.700k in 5.056653s
|
24
|
+
# resolver 68.928k (± 6.4%) i/s - 342.836k in 5.001610s
|
25
|
+
def initialize(*containers)
|
26
|
+
@containers = containers
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](name)
|
30
|
+
@containers.find { |container| container.key?(name) && (return container[name]) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# private API.
|
35
|
+
def inspect
|
36
|
+
"<Skill #{@resolver.instance_variable_get(:@containers).collect { |c| c.inspect }.join(" ")}>"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "trailblazer/operation"
|
2
|
+
require "benchmark/ips"
|
3
|
+
|
4
|
+
initialize_hash = {}
|
5
|
+
10.times do |i|
|
6
|
+
initialize_hash["bla_#{i}"] = i
|
7
|
+
end
|
8
|
+
|
9
|
+
normal_container = {}
|
10
|
+
50.times do |i|
|
11
|
+
normal_container["xbla_#{i}"] = i
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
Benchmark.ips do |x|
|
16
|
+
x.report(:merge) {
|
17
|
+
attrs = normal_container.merge(initialize_hash)
|
18
|
+
10.times do |i|
|
19
|
+
attrs["bla_8"]
|
20
|
+
end
|
21
|
+
10.times do |i|
|
22
|
+
attrs["xbla_1"]
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
x.report(:resolver) {
|
27
|
+
attrs = Trailblazer::Skill::Resolver.new(initialize_hash, normal_container)
|
28
|
+
|
29
|
+
10.times do |i|
|
30
|
+
attrs["bla_8"]
|
31
|
+
end
|
32
|
+
10.times do |i|
|
33
|
+
attrs["xbla_1"]
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Warming up --------------------------------------
|
39
|
+
# merge 3.974k i/100ms
|
40
|
+
# resolver 6.593k i/100ms
|
41
|
+
# Calculating -------------------------------------
|
42
|
+
# merge 39.678k (± 9.1%) i/s - 198.700k in 5.056653s
|
43
|
+
# resolver 68.928k (± 6.4%) i/s - 342.836k in 5.001610s
|
data/test/call_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
# DISCUSS: do we need this test?
|
4
|
+
class CallTest < Minitest::Spec
|
5
|
+
describe "::call" do
|
6
|
+
class Create < Trailblazer::Operation
|
7
|
+
def inspect
|
8
|
+
"#{@skills.inspect}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it { Create.().must_be_instance_of Trailblazer::Operation::Result }
|
13
|
+
|
14
|
+
it { Create.({}).inspect.must_equal %{<Result:true <Skill {} {\"params\"=>{}} {\"pipetree\"=>[>>operation.new]}> >} }
|
15
|
+
it { Create.(name: "Jacob").inspect.must_equal %{<Result:true <Skill {} {\"params\"=>{:name=>\"Jacob\"}} {\"pipetree\"=>[>>operation.new]}> >} }
|
16
|
+
it { Create.({ name: "Jacob" }, { policy: Object }).inspect.must_equal %{<Result:true <Skill {} {:policy=>Object, \"params\"=>{:name=>\"Jacob\"}} {\"pipetree\"=>[>>operation.new]}> >} }
|
17
|
+
|
18
|
+
#---
|
19
|
+
# success?
|
20
|
+
class Update < Trailblazer::Operation
|
21
|
+
self.& ->(input, options) { input["params"] }, after: "operation.new"
|
22
|
+
end
|
23
|
+
|
24
|
+
it { Update.(true).success?.must_equal true }
|
25
|
+
it { Update.(false).success?.must_equal false }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "dry/container"
|
3
|
+
|
4
|
+
class DryContainerTest < Minitest::Spec
|
5
|
+
my_container = Dry::Container.new
|
6
|
+
my_container.register("user_repository", -> { Object })
|
7
|
+
my_container.namespace("contract") do
|
8
|
+
register("create") { Array }
|
9
|
+
end
|
10
|
+
|
11
|
+
class Create < Trailblazer::Operation
|
12
|
+
end
|
13
|
+
|
14
|
+
it { Create.({}, {}, my_container)["user_repository"].must_equal Object }
|
15
|
+
it { Create.({}, {}, my_container)["contract.create"].must_equal Array }
|
16
|
+
# also allows our own options PLUS containers.
|
17
|
+
it { Create.({}, { "model" => String }, my_container)["model"].must_equal String }
|
18
|
+
it { Create.({}, { "model" => String }, my_container)["user_repository"].must_equal Object }
|
19
|
+
it { Create.({}, { "user_repository" => Fixnum }, my_container)["user_repository"].must_equal Fixnum }
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../../
|
3
|
+
specs:
|
4
|
+
trailblazer-operation (2.0.0)
|
5
|
+
declarative
|
6
|
+
pipetree (>= 0.0.4, < 0.1.0)
|
7
|
+
uber (>= 0.1.0, < 0.2.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
declarative (0.0.8)
|
13
|
+
uber (>= 0.0.15)
|
14
|
+
minitest (5.9.1)
|
15
|
+
pipetree (0.0.4)
|
16
|
+
rake (11.3.0)
|
17
|
+
uber (0.1.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
bundler
|
24
|
+
minitest
|
25
|
+
rake
|
26
|
+
trailblazer-operation!
|
27
|
+
|
28
|
+
BUNDLED WITH
|
29
|
+
1.13.6
|
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../../
|
3
|
+
specs:
|
4
|
+
trailblazer-operation (2.0.0)
|
5
|
+
declarative
|
6
|
+
pipetree (>= 0.0.4, < 0.1.0)
|
7
|
+
uber (>= 0.1.0, < 0.2.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
concurrent-ruby (1.0.2)
|
13
|
+
declarative (0.0.8)
|
14
|
+
uber (>= 0.0.15)
|
15
|
+
dry-auto_inject (0.4.2)
|
16
|
+
dry-container (>= 0.3.4)
|
17
|
+
dry-configurable (0.3.0)
|
18
|
+
concurrent-ruby (~> 1.0)
|
19
|
+
dry-container (0.5.0)
|
20
|
+
concurrent-ruby (~> 1.0)
|
21
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
22
|
+
dry-matcher (0.5.0)
|
23
|
+
minitest (5.9.1)
|
24
|
+
pipetree (0.0.4)
|
25
|
+
rake (11.3.0)
|
26
|
+
uber (0.1.0)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
bundler
|
33
|
+
dry-auto_inject
|
34
|
+
dry-matcher
|
35
|
+
minitest
|
36
|
+
rake
|
37
|
+
trailblazer-operation!
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
1.12.5
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class InheritanceTest < Minitest::Spec
|
4
|
+
Song = Struct.new(:id, :title, :length) do
|
5
|
+
def self.find_by(options); options[:id].nil? ? nil : new(options[:id]) end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Create < Trailblazer::Operation
|
9
|
+
self["a"] = "A"
|
10
|
+
self["b"] = "B"
|
11
|
+
self["c"] = "D"
|
12
|
+
|
13
|
+
def self.class(*skills)
|
14
|
+
Class.new(Trailblazer::Operation). tap do |klass|
|
15
|
+
skills.each { |skill| klass.heritage.record(:[]=, skill, self[skill]) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Update < Create.class("a", "b")
|
21
|
+
end
|
22
|
+
|
23
|
+
it do
|
24
|
+
Update["a"].must_equal "A"
|
25
|
+
Update["b"].must_equal "B"
|
26
|
+
Update["c"].must_equal nil
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class OperationSkillTest < Minitest::Spec
|
4
|
+
class Create < Trailblazer::Operation
|
5
|
+
end
|
6
|
+
|
7
|
+
# no dependencies provided.
|
8
|
+
it { Create.()[:not_existent].must_equal nil }
|
9
|
+
# dependencies provided.
|
10
|
+
it { Create.({}, contract: Object)[:not_existent].must_equal nil }
|
11
|
+
it { Create.({}, contract: Object)[:contract].must_equal Object }
|
12
|
+
end
|
13
|
+
|
14
|
+
class OperationCompetenceTest < Minitest::Spec
|
15
|
+
Call = ->(input, options) { input.call }
|
16
|
+
# Operation#[]
|
17
|
+
# Operation#[]=
|
18
|
+
# arbitrary options can be saved via Op#[].
|
19
|
+
class Create < Trailblazer::Operation
|
20
|
+
self.> Call
|
21
|
+
|
22
|
+
def call(*)
|
23
|
+
self["drink"] = "Little Creatures"
|
24
|
+
self["drink"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it { Create.()["drink"].must_equal "Little Creatures" }
|
29
|
+
# instance can override constructor options.
|
30
|
+
it { Create.({}, "drink" => "Four Pines")["drink"].must_equal "Little Creatures" }
|
31
|
+
# original hash doesn't get changed.
|
32
|
+
it do
|
33
|
+
Create.({}, hash = { "drink" => "Four Pines" })
|
34
|
+
hash.must_equal( { "drink" => "Four Pines" })
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Operation::[]
|
39
|
+
# Operation::[]=
|
40
|
+
class Update < Trailblazer::Operation
|
41
|
+
self.> Call
|
42
|
+
|
43
|
+
self["drink"] = "Beer"
|
44
|
+
|
45
|
+
def call(*)
|
46
|
+
self["drink"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it { Update["drink"].must_equal "Beer" }
|
51
|
+
|
52
|
+
# class-level are available on instance-level via Op#[]
|
53
|
+
it { Update.()["drink"].must_equal "Beer" }
|
54
|
+
|
55
|
+
# runtime constructor options can override class-level.
|
56
|
+
it { Update.({}, "drink" => "Little Creatures")["drink"].must_equal "Little Creatures" }
|
57
|
+
|
58
|
+
# instance can override class-level
|
59
|
+
class Delete < Trailblazer::Operation
|
60
|
+
self.> Call
|
61
|
+
|
62
|
+
self["drink"] = "Beer"
|
63
|
+
|
64
|
+
def call(*)
|
65
|
+
self["drink"] = "Little Creatures"
|
66
|
+
self["drink"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Op#[]= can override class-level...
|
71
|
+
it { Delete.()["drink"].must_equal "Little Creatures" }
|
72
|
+
# ...but it doesn't change class-level.
|
73
|
+
it { Delete["drink"].must_equal "Beer" }
|
74
|
+
|
75
|
+
# we don't really need this test.
|
76
|
+
class Reward < Trailblazer::Operation
|
77
|
+
self["drink"] = "Beer"
|
78
|
+
end
|
79
|
+
|
80
|
+
it { Reward.()["drink"].must_equal "Beer" }
|
81
|
+
end
|
82
|
+
|
83
|
+
# {
|
84
|
+
# user_repository: ..,
|
85
|
+
# current_user: ..,
|
86
|
+
# }
|
87
|
+
|
88
|
+
|
89
|
+
# 1. initialize(params, {})
|
90
|
+
# 2. extend AutoInject[] shouldn't override constructor but simply pass injected dependencies in second arg (by adding dependencies to hash).
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PipetreeTest < Minitest::Spec
|
4
|
+
module Validate
|
5
|
+
extend Trailblazer::Operation::Stepable
|
6
|
+
|
7
|
+
def self.import!(operation, pipe)
|
8
|
+
pipe.(:>, ->{ snippet }, name: "validate", before: "operation.new")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#---
|
13
|
+
# ::|
|
14
|
+
# without options
|
15
|
+
class Create < Trailblazer::Operation
|
16
|
+
self.| Validate[]
|
17
|
+
end
|
18
|
+
|
19
|
+
it { Create["pipetree"].inspect.must_equal %{[>validate,>>operation.new]} }
|
20
|
+
|
21
|
+
# without any options or []
|
22
|
+
class New < Trailblazer::Operation
|
23
|
+
self.| Validate
|
24
|
+
end
|
25
|
+
|
26
|
+
it { New["pipetree"].inspect.must_equal %{[>validate,>>operation.new]} }
|
27
|
+
|
28
|
+
# with options
|
29
|
+
class Update < Trailblazer::Operation
|
30
|
+
self.| Validate, after: "operation.new"
|
31
|
+
end
|
32
|
+
|
33
|
+
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,>validate]} }
|
34
|
+
|
35
|
+
# with :symbol
|
36
|
+
class Delete < Trailblazer::Operation
|
37
|
+
self.| :call!
|
38
|
+
|
39
|
+
def call!(options)
|
40
|
+
self["x"] = options["params"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it { Delete.("yo")["x"].must_equal "yo" }
|
45
|
+
|
46
|
+
# proc arguments
|
47
|
+
class Forward < Trailblazer::Operation
|
48
|
+
self.| ->(input, options) { puts "@@@@@ #{input.inspect}"; puts "@@@@@ #{options.inspect}" }
|
49
|
+
end
|
50
|
+
|
51
|
+
it { skip; Forward.({ id: 1 }) }
|
52
|
+
|
53
|
+
#---
|
54
|
+
# ::>, ::<, ::>>, :&
|
55
|
+
# with proc, method, callable.
|
56
|
+
class Right < Trailblazer::Operation
|
57
|
+
self.> ->(input, options) { options[">"] = options["params"][:id] }
|
58
|
+
|
59
|
+
self.> :method_name!
|
60
|
+
def method_name!(options); self["method_name!"] = options["params"][:id] end
|
61
|
+
|
62
|
+
class MyCallable
|
63
|
+
include Uber::Callable
|
64
|
+
def call(operation, options); operation["callable"] = options["params"][:id] end
|
65
|
+
end
|
66
|
+
self.> MyCallable.new
|
67
|
+
end
|
68
|
+
|
69
|
+
it { Right.( id: 1 ).slice(">", "method_name!", "callable").must_equal [1, 1, 1] }
|
70
|
+
it { Right["pipetree"].inspect.must_equal %{[>>operation.new,>self,>return,>#<PipetreeTest::Right::MyCallable:>]} }
|
71
|
+
|
72
|
+
#---
|
73
|
+
# inheritance
|
74
|
+
class Righter < Right
|
75
|
+
self.> ->(input, options) { options["righter"] = true }
|
76
|
+
end
|
77
|
+
|
78
|
+
it { Righter.( id: 1 ).slice(">", "method_name!", "callable", "righter").must_equal [1, 1, 1, true] }
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# args: operation, skills
|
data/test/result_test.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ResultTest < Minitest::Spec
|
4
|
+
let (:success) { Trailblazer::Operation::Result.new(true, "x"=> String) }
|
5
|
+
it { success.success?.must_equal true }
|
6
|
+
it { success.failure?.must_equal false }
|
7
|
+
# it { success["success?"].must_equal true }
|
8
|
+
# it { success["failure?"].must_equal false }
|
9
|
+
it { success["x"].must_equal String }
|
10
|
+
it { success["not-existant"].must_equal nil }
|
11
|
+
it { success.slice("x").must_equal [String] }
|
12
|
+
it { success.inspect.must_equal %{<Result:true {\"x\"=>String} >} }
|
13
|
+
|
14
|
+
class Create < Trailblazer::Operation
|
15
|
+
self.> ->(input, options) { input.call }
|
16
|
+
|
17
|
+
def call(*)
|
18
|
+
self[:message] = "Result objects are actually quite handy!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# #result[]= allows to set arbitrary k/v pairs.
|
23
|
+
it { Create.()[:message].must_equal "Result objects are actually quite handy!" }
|
24
|
+
end
|
data/test/skill_test.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "trailblazer/skill"
|
3
|
+
|
4
|
+
class SkillTest < Minitest::Spec
|
5
|
+
describe "Skill" do
|
6
|
+
it do
|
7
|
+
class_level_container = {
|
8
|
+
"contract.class" => Object,
|
9
|
+
"model.class" => String
|
10
|
+
}
|
11
|
+
|
12
|
+
runtime_skills = {
|
13
|
+
"contract" => MyContract=Class.new,
|
14
|
+
"model.class" => Integer
|
15
|
+
}
|
16
|
+
|
17
|
+
mutable_options = {}
|
18
|
+
|
19
|
+
skill = Trailblazer::Skill.new(mutable_options, runtime_skills, class_level_container)
|
20
|
+
|
21
|
+
# non-existent key.
|
22
|
+
skill[:nope].must_equal nil
|
23
|
+
|
24
|
+
# from runtime.
|
25
|
+
skill["contract"].must_equal MyContract
|
26
|
+
# from compile-time.
|
27
|
+
skill["contract.class"].must_equal Object
|
28
|
+
# runtime supersedes compile-time.
|
29
|
+
skill["model.class"].must_equal Integer
|
30
|
+
|
31
|
+
skill["model.class"] = Fixnum
|
32
|
+
skill["model.class"].must_equal Fixnum
|
33
|
+
|
34
|
+
# add new tuple.
|
35
|
+
skill["user.current"] = "Todd"
|
36
|
+
|
37
|
+
# options we add get added to the hash.
|
38
|
+
mutable_options.inspect.must_equal %{{"model.class"=>Fixnum, "user.current"=>"Todd"}}
|
39
|
+
# original container don't get changed
|
40
|
+
class_level_container.inspect.must_equal %{{"contract.class"=>Object, "model.class"=>String}}
|
41
|
+
runtime_skills.inspect.must_equal %{{"contract"=>SkillTest::MyContract, "model.class"=>Integer}}
|
42
|
+
|
43
|
+
# setting false.
|
44
|
+
skill[:valid] = false
|
45
|
+
skill[:valid].must_equal false
|
46
|
+
|
47
|
+
# setting nil.
|
48
|
+
skill[:valid] = nil
|
49
|
+
skill[:valid].must_equal nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# resolve: prefer @instance_attrs over competences
|
54
|
+
# or instace_atrt is competences
|
55
|
+
|
56
|
+
# dependencies = { current_user: Runtime::User..., container: BLA }
|
57
|
+
# dependencies[:current_user]
|
58
|
+
# dependencies["user.create.contract"] # delegates to container, somehow.
|
59
|
+
|
60
|
+
# Create.(params, dependencies) # not sure if op should build this runtime dependencies hash or if it comes from outside.
|
61
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'trailblazer/operation/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "trailblazer-operation"
|
7
|
+
spec.version = Trailblazer::Operation::VERSION
|
8
|
+
spec.authors = ["Nick Sutterer"]
|
9
|
+
spec.email = ["apotonick@gmail.com"]
|
10
|
+
spec.description = %q{Trailblazer's operation object.}
|
11
|
+
spec.summary = %q{Trailblazer's operation object with dependency management and pipetree flow.}
|
12
|
+
spec.homepage = "http://trailblazer.to"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
|
21
|
+
spec.add_dependency "uber", ">= 0.1.0", "< 0.2.0"
|
22
|
+
spec.add_dependency "declarative"
|
23
|
+
spec.add_dependency "pipetree", ">= 0.0.4", "< 0.1.0"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "minitest"
|
28
|
+
|
29
|
+
spec.required_ruby_version = '>= 1.9.3'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trailblazer-operation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Sutterer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uber
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.2.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: declarative
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pipetree
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.0.4
|
54
|
+
- - "<"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.1.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.0.4
|
64
|
+
- - "<"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.1.0
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: bundler
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rake
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: minitest
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
description: Trailblazer's operation object.
|
110
|
+
email:
|
111
|
+
- apotonick@gmail.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files: []
|
115
|
+
files:
|
116
|
+
- ".travis.yml"
|
117
|
+
- CHANGES.md
|
118
|
+
- Gemfile
|
119
|
+
- README.md
|
120
|
+
- Rakefile
|
121
|
+
- lib/trailblazer/operation.rb
|
122
|
+
- lib/trailblazer/operation/generic.rb
|
123
|
+
- lib/trailblazer/operation/pipetree.rb
|
124
|
+
- lib/trailblazer/operation/result.rb
|
125
|
+
- lib/trailblazer/operation/skill.rb
|
126
|
+
- lib/trailblazer/operation/stepable.rb
|
127
|
+
- lib/trailblazer/operation/version.rb
|
128
|
+
- lib/trailblazer/skill.rb
|
129
|
+
- test/benchmark/skill_resolver_benchmark.rb
|
130
|
+
- test/call_test.rb
|
131
|
+
- test/dry_container_test.rb
|
132
|
+
- test/gemfiles/Gemfile.ruby-1.9
|
133
|
+
- test/gemfiles/Gemfile.ruby-1.9.lock
|
134
|
+
- test/gemfiles/Gemfile.ruby-2.3
|
135
|
+
- test/gemfiles/Gemfile.ruby-2.3.lock
|
136
|
+
- test/inheritance_test.rb
|
137
|
+
- test/operation_skill_test.rb
|
138
|
+
- test/pipetree_test.rb
|
139
|
+
- test/result_test.rb
|
140
|
+
- test/skill_test.rb
|
141
|
+
- test/test_helper.rb
|
142
|
+
- trailblazer-operation.gemspec
|
143
|
+
homepage: http://trailblazer.to
|
144
|
+
licenses:
|
145
|
+
- MIT
|
146
|
+
metadata: {}
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: 1.9.3
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubyforge_project:
|
163
|
+
rubygems_version: 2.6.3
|
164
|
+
signing_key:
|
165
|
+
specification_version: 4
|
166
|
+
summary: Trailblazer's operation object with dependency management and pipetree flow.
|
167
|
+
test_files:
|
168
|
+
- test/benchmark/skill_resolver_benchmark.rb
|
169
|
+
- test/call_test.rb
|
170
|
+
- test/dry_container_test.rb
|
171
|
+
- test/gemfiles/Gemfile.ruby-1.9
|
172
|
+
- test/gemfiles/Gemfile.ruby-1.9.lock
|
173
|
+
- test/gemfiles/Gemfile.ruby-2.3
|
174
|
+
- test/gemfiles/Gemfile.ruby-2.3.lock
|
175
|
+
- test/inheritance_test.rb
|
176
|
+
- test/operation_skill_test.rb
|
177
|
+
- test/pipetree_test.rb
|
178
|
+
- test/result_test.rb
|
179
|
+
- test/skill_test.rb
|
180
|
+
- test/test_helper.rb
|