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.
@@ -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
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem install bundler
4
+ matrix:
5
+ include:
6
+ - rvm: 1.9.3
7
+ gemfile: "test/gemfiles/Gemfile.ruby-1.9"
8
+ - rvm: 2.1
9
+ gemfile: Gemfile
10
+ - rvm: 2.3.1
11
+ gemfile: Gemfile
@@ -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"
@@ -0,0 +1,3 @@
1
+ ## Goal
2
+
3
+ * Make `Operation` an imutable object without having to expose @instance variables. This is now done via the Result object.
@@ -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,12 @@
1
+ module Trailblazer
2
+ # Generic initializer for the operation.
3
+ module Operation::Generic
4
+ def initialize(skills={})
5
+ @skills = skills
6
+ end
7
+
8
+ # dependency injection interface
9
+ extend Uber::Delegates
10
+ delegates :@skills, :[], :[]=
11
+ end
12
+ 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,7 @@
1
+ module Trailblazer
2
+ # The Trailblazer-style operation.
3
+ # Note that you don't have to use our "opinionated" version with result object, skills, etc.
4
+ class Operation
5
+ VERSION = "0.0.2"
6
+ end
7
+ 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
@@ -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,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec path: "../../"
@@ -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,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec path: "../../"
3
+
4
+ gem "dry-auto_inject"
5
+ gem "dry-matcher"
@@ -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
@@ -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
@@ -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
+
@@ -0,0 +1,2 @@
1
+ require "minitest/autorun"
2
+ require "trailblazer/operation"
@@ -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