trailblazer-operation 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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