trailblazer 2.0.7 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +556 -0
- data/.travis.yml +6 -10
- data/CHANGES.md +83 -1
- data/COMM-LICENSE +46 -75
- data/CONTRIBUTING.md +179 -0
- data/Gemfile +0 -27
- data/{LICENSE.txt → LICENSE} +4 -4
- data/README.md +39 -138
- data/Rakefile +2 -19
- data/lib/trailblazer.rb +3 -17
- data/lib/trailblazer/version.rb +3 -1
- data/test/test_helper.rb +12 -3
- data/trailblazer.gemspec +10 -14
- metadata +22 -147
- data/doc/Trb-The-Stack.png +0 -0
- data/doc/operation-2017.png +0 -0
- data/doc/trb.jpg +0 -0
- data/lib/trailblazer/dsl.rb +0 -47
- data/lib/trailblazer/operation/auto_inject.rb +0 -47
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/contract.rb +0 -46
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -60
- data/lib/trailblazer/operation/module.rb +0 -29
- data/lib/trailblazer/operation/nested.rb +0 -113
- data/lib/trailblazer/operation/persist.rb +0 -10
- data/lib/trailblazer/operation/policy.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -31
- data/lib/trailblazer/operation/rescue.rb +0 -21
- data/lib/trailblazer/operation/test.rb +0 -17
- data/lib/trailblazer/operation/validate.rb +0 -68
- data/lib/trailblazer/operation/wrap.rb +0 -25
- data/test/docs/auto_inject_test.rb +0 -30
- data/test/docs/contract_test.rb +0 -525
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/fast_test.rb +0 -164
- data/test/docs/guard_test.rb +0 -169
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -334
- data/test/docs/operation_test.rb +0 -408
- data/test/docs/policy_test.rb +0 -2
- data/test/docs/pundit_test.rb +0 -133
- data/test/docs/representer_test.rb +0 -268
- data/test/docs/rescue_test.rb +0 -154
- data/test/docs/wrap_test.rb +0 -183
- data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
- data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
- data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
- data/test/module_test.rb +0 -100
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/contract_test.rb +0 -420
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/dsl/contract_test.rb +0 -294
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -60
- data/test/operation/params_test.rb +0 -36
- data/test/operation/persist_test.rb +0 -44
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/pundit_test.rb +0 -104
- data/test/operation/representer_test.rb +0 -254
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
@@ -1,60 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class ModelTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:id) do
|
5
|
-
def self.find(id); new(id) end
|
6
|
-
def self.find_by(id:nil); id.nil? ? nil : new(id) end
|
7
|
-
end
|
8
|
-
|
9
|
-
#---
|
10
|
-
# use Model semantics, no customizations.
|
11
|
-
class Create < Trailblazer::Operation
|
12
|
-
step Model Song, :new
|
13
|
-
end
|
14
|
-
|
15
|
-
# :new new.
|
16
|
-
it { Create.({})["model"].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
|
17
|
-
|
18
|
-
class Update < Create
|
19
|
-
step Model( Song, :find ), override: true
|
20
|
-
end
|
21
|
-
|
22
|
-
# :find it
|
23
|
-
it { Update.({ id: 1 })["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
|
24
|
-
|
25
|
-
#- inheritance
|
26
|
-
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build]} }
|
27
|
-
|
28
|
-
#---
|
29
|
-
# :find_by, exceptionless.
|
30
|
-
class Find < Trailblazer::Operation
|
31
|
-
step Model Song, :find_by
|
32
|
-
step :process
|
33
|
-
|
34
|
-
def process(*); self["x"] = true end
|
35
|
-
end
|
36
|
-
|
37
|
-
# can't find model.
|
38
|
-
#- result object, model
|
39
|
-
it do
|
40
|
-
Find.(id: nil)["result.model"].failure?.must_equal true
|
41
|
-
Find.(id: nil)["x"].must_equal nil
|
42
|
-
Find.(id: nil).failure?.must_equal true
|
43
|
-
end
|
44
|
-
|
45
|
-
#- result object, model
|
46
|
-
it do
|
47
|
-
Find.(id: 9)["result.model"].success?.must_equal true
|
48
|
-
Find.(id: 9)["x"].must_equal true
|
49
|
-
Find.(id: 9)["model"].inspect.must_equal %{#<struct ModelTest::Song id=9>}
|
50
|
-
end
|
51
|
-
|
52
|
-
# #---
|
53
|
-
# # creating the model before operation instantiation (ex Model::External)
|
54
|
-
# class Show < Create
|
55
|
-
# extend Model::BuildMethods # FIXME: how do we communicate that and prevent the include from Model[] ?
|
56
|
-
# step Model( Song, :update ), before: "operation.new"
|
57
|
-
# end
|
58
|
-
|
59
|
-
# it { Show.({id: 1})["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
|
60
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "trailblazer/operation/params"
|
3
|
-
|
4
|
-
class OperationParamsTest < MiniTest::Spec
|
5
|
-
#---
|
6
|
-
# in call(params) and process(params), argument is always self["params"]
|
7
|
-
class Find < Trailblazer::Operation
|
8
|
-
def process(params); self["eql?"] = self["params"].object_id==params.object_id end
|
9
|
-
end
|
10
|
-
class Seek < Trailblazer::Operation
|
11
|
-
include Test::ReturnCall
|
12
|
-
def call(params); self["eql?"] = self["params"].object_id==params.object_id end
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
it { Find.({ id: 1 })["eql?"].must_equal true }
|
17
|
-
# self["params"] is what gets passed in.
|
18
|
-
it { Find.({ id: 1 })["params"].must_equal({ id: 1 }) }
|
19
|
-
it { Seek.({ id: 1 }).must_equal true }
|
20
|
-
|
21
|
-
|
22
|
-
#---
|
23
|
-
# change self["params"] via Operation#
|
24
|
-
class Create < Trailblazer::Operation
|
25
|
-
include Params
|
26
|
-
def process(params) self[:x] = params end
|
27
|
-
def params!(params); params.merge(garrett: "Rocks!") end
|
28
|
-
end
|
29
|
-
|
30
|
-
# allows you changing params in #setup_params!.
|
31
|
-
it {
|
32
|
-
result = Create.( id: 1 )
|
33
|
-
result["params"].inspect.must_equal %{{:id=>1, :garrett=>\"Rocks!\"}}
|
34
|
-
result["params.original"].inspect.must_equal %{{:id=>1}}
|
35
|
-
}
|
36
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PersistTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:title, :saved) do
|
5
|
-
def save; title=="Fail!" ? false : self.saved = true; end
|
6
|
-
end
|
7
|
-
|
8
|
-
class Create < Trailblazer::Operation
|
9
|
-
extend Contract::DSL
|
10
|
-
contract do
|
11
|
-
property :title
|
12
|
-
end
|
13
|
-
|
14
|
-
step Model( Song, :new )
|
15
|
-
step Contract::Build()
|
16
|
-
step Contract::Validate()
|
17
|
-
failure ->(options) { options["1. fail"] = "Validate" }
|
18
|
-
step Contract::Persist()
|
19
|
-
failure ->(options) { options["2. fail"] = "Persist" }
|
20
|
-
end
|
21
|
-
|
22
|
-
it { Create.(title: "In Recital")["model"].title.must_equal "In Recital" }
|
23
|
-
it { Create.(title: "In Recital")["model"].saved.must_equal true }
|
24
|
-
# failure
|
25
|
-
it do
|
26
|
-
result = Create.(title: "Fail!")
|
27
|
-
result["model"].saved.must_equal nil
|
28
|
-
result["model"].title.must_equal "Fail!"
|
29
|
-
result["2. fail"].must_equal "Persist"
|
30
|
-
result.success?.must_equal false
|
31
|
-
end
|
32
|
-
|
33
|
-
#---
|
34
|
-
#- inheritance
|
35
|
-
class Update < Create
|
36
|
-
end
|
37
|
-
|
38
|
-
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,<persist_test.rb:17,>persist.save,<persist_test.rb:19]} }
|
39
|
-
|
40
|
-
#---
|
41
|
-
it do
|
42
|
-
skip "show how save! could be applied and how we could rescue and deviate to left track"
|
43
|
-
end
|
44
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PipedreamTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:title)
|
5
|
-
|
6
|
-
class Create < Trailblazer::Operation
|
7
|
-
class MyContract < Reform::Form
|
8
|
-
property :title
|
9
|
-
end
|
10
|
-
|
11
|
-
class Auth
|
12
|
-
def initialize(user, model); @user, @model = user, model end
|
13
|
-
def user_and_model?; @user == Module && @model.class == Song end
|
14
|
-
end
|
15
|
-
|
16
|
-
# design principles:
|
17
|
-
# * include as less code as possible into the op class.
|
18
|
-
# * make the flow super explicit without making it cryptic (only 3 new operators)
|
19
|
-
# * avoid including DSL modules in favor of passing those configurations directly to the "step".
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
step Model[ Song, :new ] # model!)
|
24
|
-
step Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
25
|
-
step Contract[ MyContract]
|
26
|
-
step Policy[ Auth, :user_and_model?]
|
27
|
-
failure Contract[ MyContract]
|
28
|
-
|
29
|
-
# step :model
|
30
|
-
# step :guard
|
31
|
-
# step :contract
|
32
|
-
|
33
|
-
|
34
|
-
# ok Model[Song, :new] # model!)
|
35
|
-
# ok Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
36
|
-
# ok Contract[MyContract]
|
37
|
-
# fail Contract[MyContract]
|
38
|
-
# step> "contract"
|
39
|
-
|
40
|
-
# | :bla
|
41
|
-
# | ->
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
# TODO: test with contract constant (done).
|
46
|
-
# test with inline contract.
|
47
|
-
# test with override contract!.
|
48
|
-
|
49
|
-
it do
|
50
|
-
puts Create["pipetree"].inspect(style: :rows)
|
51
|
-
result = Create.({}, { "current_user" => Module })
|
52
|
-
|
53
|
-
result["model"].inspect.must_equal %{#<struct PipedreamTest::Song title=nil>}
|
54
|
-
result["result.policy"].success?.must_equal true
|
55
|
-
result["contract"].class.superclass.must_equal Reform::Form
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
# self["pipetree"] = ::Pipetree[
|
4
|
-
# Trailblazer::Operation::New,
|
5
|
-
# # SetupParams,
|
6
|
-
# Trailblazer::Operation::Model::Build,
|
7
|
-
# Trailblazer::Operation::Model::Assign,
|
8
|
-
# Trailblazer::Operation::Call,
|
9
|
-
# ]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# def deserialize(*)
|
14
|
-
# super
|
15
|
-
# self.datetime = DateTime.parse("#{date} #{time}")
|
16
|
-
# end
|
17
|
-
|
18
|
-
class PipetreeTest < Minitest::Spec
|
19
|
-
Song = Struct.new(:title)
|
20
|
-
|
21
|
-
class Create < Trailblazer::Operation
|
22
|
-
include Builder
|
23
|
-
include Pipetree # this will add the functions, again, unfortunately. definitely an error source.
|
24
|
-
end
|
25
|
-
|
26
|
-
it { Create["pipetree"].inspect.must_equal %{[>>Build,>>New,>>Call,Result::Build,>>New,>>Call,Result::Build]} }
|
27
|
-
|
28
|
-
#---
|
29
|
-
# playground
|
30
|
-
require "trailblazer/operation/policy"
|
31
|
-
require "trailblazer/operation/guard"
|
32
|
-
|
33
|
-
class Edit < Trailblazer::Operation
|
34
|
-
include Builder
|
35
|
-
include Policy::Guard
|
36
|
-
include Contract::Step
|
37
|
-
contract do
|
38
|
-
property :title
|
39
|
-
validates :title, presence: true
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
MyValidate = ->(input, options) { res= input.validate(options["params"]) { |f| f.sync } }
|
44
|
-
# we can have a separate persist step and wrap in transaction. where do we pass contract, though?
|
45
|
-
step MyValidate, before: Call #replace: Contract::ValidLegacySwitch
|
46
|
-
#
|
47
|
-
MyAfterSave = ->(input, options) { input["after_save"] = true }
|
48
|
-
success MyAfterSave, after: MyValidate
|
49
|
-
|
50
|
-
ValidateFailureLogger = ->(input, options) { input["validate fail"] = true }
|
51
|
-
failure ValidateFailureLogger, after: MyValidate
|
52
|
-
|
53
|
-
success ->(input, options) { input.process(options["params"]) }, replace: Call, name: "my.params"
|
54
|
-
|
55
|
-
include Model
|
56
|
-
|
57
|
-
LogBreach = ->(input, options) { input.log_breach! }
|
58
|
-
|
59
|
-
failure LogBreach, after: Policy::Evaluate
|
60
|
-
|
61
|
-
model Song
|
62
|
-
policy ->(*) { self["current_user"] }
|
63
|
-
|
64
|
-
def log_breach!
|
65
|
-
self["breach"] = true
|
66
|
-
end
|
67
|
-
|
68
|
-
def process(params)
|
69
|
-
self["my.valid"] = true
|
70
|
-
end
|
71
|
-
|
72
|
-
self["pipetree"]._insert(Contract::ValidLegacySwitch, {delete: true}, nil, nil)
|
73
|
-
end
|
74
|
-
|
75
|
-
puts Edit["pipetree"].inspect(style: :rows)
|
76
|
-
|
77
|
-
it { Edit["pipetree"].inspect.must_equal %{[>>operation.build,>>operation.new,&model.build,&policy.guard.evaluate,<LogBreach,>contract.build,&MyValidate,<ValidateFailureLogger,>MyAfterSave,>my.params["params"]]} }
|
78
|
-
|
79
|
-
# valid case.
|
80
|
-
it {
|
81
|
-
# puts "valid"
|
82
|
-
# puts Edit["pipetree"].inspect(style: :rows)
|
83
|
-
result = Edit.({ title: "Stupid 7" }, "current_user" => true)
|
84
|
-
# puts "success! #{result.inspect}"
|
85
|
-
result["my.valid"].must_equal true
|
86
|
-
result["breach"].must_equal nil
|
87
|
-
result["after_save"].must_equal true
|
88
|
-
result["validate fail"].must_equal nil
|
89
|
-
}
|
90
|
-
# beach! i mean breach!
|
91
|
-
it {
|
92
|
-
# puts "beach"
|
93
|
-
# puts Edit["pipetree"].inspect(style: :rows)
|
94
|
-
result = Edit.({})
|
95
|
-
# puts "@@@@@ #{result.inspect}"
|
96
|
-
result["my.valid"].must_equal nil
|
97
|
-
result["breach"].must_equal true
|
98
|
-
result["validate fail"].must_equal true
|
99
|
-
result["after_save"].must_equal nil
|
100
|
-
}
|
101
|
-
end
|
102
|
-
|
103
|
-
# TODO: show the execution path in pipetree
|
104
|
-
# unified result.contract, result.policy interface
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
require "trailblazer/operation/present"
|
4
|
-
|
5
|
-
class PresentTest < Minitest::Spec
|
6
|
-
class Create < Trailblazer::Operation
|
7
|
-
include Test::ReturnCall
|
8
|
-
include Present
|
9
|
-
|
10
|
-
include Model::Builder
|
11
|
-
def model!(*); Object end
|
12
|
-
|
13
|
-
def call(params)
|
14
|
-
"#call run!"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
it do
|
19
|
-
result = Create.present
|
20
|
-
result["model"].must_equal Object
|
21
|
-
end
|
22
|
-
|
23
|
-
it { Create.().must_equal "#call run!" }
|
24
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "trailblazer/operation/policy"
|
3
|
-
|
4
|
-
class PolicyTest < Minitest::Spec
|
5
|
-
Song = Struct.new(:id) do
|
6
|
-
def self.find(id); new(id) end
|
7
|
-
end
|
8
|
-
|
9
|
-
class Auth
|
10
|
-
def initialize(user, model); @user, @model = user, model end
|
11
|
-
def only_user?; @user == Module && @model.nil? end
|
12
|
-
def user_object?; @user == Object end
|
13
|
-
def user_and_model?; @user == Module && @model.class == Song end
|
14
|
-
def inspect; "<Auth: user:#{@user.inspect}, model:#{@model.inspect}>" end
|
15
|
-
end
|
16
|
-
|
17
|
-
#---
|
18
|
-
# Instance-level: Only policy, no model
|
19
|
-
class Create < Trailblazer::Operation
|
20
|
-
step Policy::Pundit( Auth, :only_user? )
|
21
|
-
step :process
|
22
|
-
|
23
|
-
def process(*)
|
24
|
-
self["process"] = true
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# successful.
|
29
|
-
it do
|
30
|
-
result = Create.({}, "current_user" => Module)
|
31
|
-
result["process"].must_equal true
|
32
|
-
#- result object, policy
|
33
|
-
result["result.policy.default"].success?.must_equal true
|
34
|
-
result["result.policy.default"]["message"].must_equal nil
|
35
|
-
# result[:valid].must_equal nil
|
36
|
-
result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:nil>}
|
37
|
-
end
|
38
|
-
# breach.
|
39
|
-
it do
|
40
|
-
result = Create.({}, "current_user" => nil)
|
41
|
-
result["process"].must_equal nil
|
42
|
-
#- result object, policy
|
43
|
-
result["result.policy.default"].success?.must_equal false
|
44
|
-
result["result.policy.default"]["message"].must_equal "Breach"
|
45
|
-
end
|
46
|
-
# inject different policy.Condition it { Create.({}, "current_user" => Object, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_equal true }
|
47
|
-
it { Create.({}, "current_user" => Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_equal nil }
|
48
|
-
|
49
|
-
|
50
|
-
#---
|
51
|
-
# inheritance, adding Model
|
52
|
-
class Show < Create
|
53
|
-
step Model( Song, :new ), before: "policy.default.eval"
|
54
|
-
end
|
55
|
-
|
56
|
-
it { Show["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>policy.default.eval,>process]} }
|
57
|
-
|
58
|
-
# invalid because user AND model.
|
59
|
-
it do
|
60
|
-
result = Show.({}, "current_user" => Module)
|
61
|
-
result["process"].must_equal nil
|
62
|
-
result["model"].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
|
63
|
-
# result["policy"].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
|
64
|
-
end
|
65
|
-
|
66
|
-
# valid because new policy.
|
67
|
-
it do
|
68
|
-
# puts Show["pipetree"].inspect
|
69
|
-
result = Show.({}, "current_user" => Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_and_model?))
|
70
|
-
result["process"].must_equal true
|
71
|
-
result["model"].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
|
72
|
-
result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=nil>>}
|
73
|
-
end
|
74
|
-
|
75
|
-
##--
|
76
|
-
# TOOOODOOO: Policy and Model before Build ("External" or almost Resolver)
|
77
|
-
class Edit < Trailblazer::Operation
|
78
|
-
step Model Song, :find
|
79
|
-
step Policy::Pundit( Auth, :user_and_model? )
|
80
|
-
step :process
|
81
|
-
|
82
|
-
def process(*); self["process"] = true end
|
83
|
-
end
|
84
|
-
|
85
|
-
# successful.
|
86
|
-
it do
|
87
|
-
result = Edit.({ id: 1 }, "current_user" => Module)
|
88
|
-
result["process"].must_equal true
|
89
|
-
result["model"].inspect.must_equal %{#<struct PolicyTest::Song id=1>}
|
90
|
-
result["result.policy.default"].success?.must_equal true
|
91
|
-
result["result.policy.default"]["message"].must_equal nil
|
92
|
-
# result[:valid].must_equal nil
|
93
|
-
result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=1>>}
|
94
|
-
end
|
95
|
-
|
96
|
-
# breach.
|
97
|
-
it do
|
98
|
-
result = Edit.({ id: 4 }, "current_user" => nil)
|
99
|
-
result["model"].inspect.must_equal %{#<struct PolicyTest::Song id=4>}
|
100
|
-
result["process"].must_equal nil
|
101
|
-
result["result.policy.default"].success?.must_equal false
|
102
|
-
result["result.policy.default"]["message"].must_equal "Breach"
|
103
|
-
end
|
104
|
-
end
|
@@ -1,254 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "representable/json"
|
3
|
-
|
4
|
-
class RepresenterTest < MiniTest::Spec
|
5
|
-
Album = Struct.new(:title, :artist)
|
6
|
-
Artist = Struct.new(:name)
|
7
|
-
|
8
|
-
class Create < Trailblazer::Operation
|
9
|
-
include Contract::Explicit
|
10
|
-
include Representer
|
11
|
-
include Representer::InferFromContract
|
12
|
-
attr_reader :model # FIXME: all we want is #model.
|
13
|
-
|
14
|
-
contract do
|
15
|
-
property :title
|
16
|
-
validates :title, presence: true
|
17
|
-
property :artist, populate_if_empty: Artist do
|
18
|
-
property :name
|
19
|
-
validates :name, presence: true
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def call(params)
|
24
|
-
self["model"] = Album.new # NO artist!!!
|
25
|
-
validate(params[:album], model: self["model"])
|
26
|
-
self
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
# Infers representer from contract, no customization.
|
32
|
-
class Show < Create
|
33
|
-
def call(params)
|
34
|
-
self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
|
35
|
-
self
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
# Infers representer, adds hypermedia.
|
41
|
-
require "roar/json/hal"
|
42
|
-
class HypermediaCreate < Create
|
43
|
-
representer do
|
44
|
-
include Roar::JSON::HAL
|
45
|
-
|
46
|
-
link(:self) { "//album/#{represented.title}" }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
class HypermediaShow < HypermediaCreate
|
51
|
-
def call(params)
|
52
|
-
self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
|
53
|
-
self
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
# rendering
|
59
|
-
# generic contract -> representer
|
60
|
-
it { Show.().to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"}}} }
|
61
|
-
|
62
|
-
# contract -> representer with hypermedia
|
63
|
-
it do
|
64
|
-
HypermediaShow.().to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}}
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
# parsing
|
69
|
-
it do
|
70
|
-
op = Create.(album: %{{"title":"Run For Cover","artist":{"name":"Gary Moore"}}})
|
71
|
-
op.contract.title.must_equal "Run For Cover"
|
72
|
-
op.contract.artist.name.must_equal "Gary Moore"
|
73
|
-
end
|
74
|
-
|
75
|
-
it do
|
76
|
-
op = HypermediaCreate.(album: %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}})
|
77
|
-
op.contract.title.must_equal "After The War"
|
78
|
-
op.contract.artist.name.must_equal "Gary Moore"
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# explicit representer set with ::representer_class=.
|
86
|
-
require "roar/decorator"
|
87
|
-
class JsonApiCreate < Trailblazer::Operation
|
88
|
-
include Contract::Explicit
|
89
|
-
include Representer
|
90
|
-
attr_reader :model
|
91
|
-
|
92
|
-
contract do # we still need contract as the representer writes to the contract twin.
|
93
|
-
property :title
|
94
|
-
end
|
95
|
-
|
96
|
-
class AlbumRepresenter < Roar::Decorator
|
97
|
-
include Roar::JSON
|
98
|
-
property :title
|
99
|
-
end
|
100
|
-
|
101
|
-
# FIXME: this won't inherit, of course.
|
102
|
-
# self["representer.class"] = AlbumRepresenter
|
103
|
-
representer AlbumRepresenter
|
104
|
-
|
105
|
-
def call(params)
|
106
|
-
self["model"] = Album.new # NO artist!!!
|
107
|
-
validate(params[:album], model: self["model"])
|
108
|
-
self
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
class JsonApiShow < JsonApiCreate
|
113
|
-
def call(params)
|
114
|
-
self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
|
115
|
-
self
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# render.
|
120
|
-
it do
|
121
|
-
JsonApiShow.().to_json.must_equal %{{"title":"After The War"}}
|
122
|
-
end
|
123
|
-
|
124
|
-
# parse.
|
125
|
-
it do
|
126
|
-
JsonApiCreate.(album: %{{"title":"Run For Cover"}}).contract.title.must_equal "Run For Cover"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
class InternalRepresenterAPITest < MiniTest::Spec
|
131
|
-
Song = Struct.new(:id)
|
132
|
-
|
133
|
-
describe "#represented" do
|
134
|
-
class Show < Trailblazer::Operation
|
135
|
-
include Contract::Explicit
|
136
|
-
include Representer, Model
|
137
|
-
model Song, :create
|
138
|
-
|
139
|
-
representer do
|
140
|
-
property :class
|
141
|
-
end
|
142
|
-
|
143
|
-
def call(*)
|
144
|
-
self
|
145
|
-
end
|
146
|
-
|
147
|
-
def model # FIXME.
|
148
|
-
self["model"]
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
it "uses #model as represented, per default" do
|
153
|
-
Show.({}).to_json.must_equal '{"class":"InternalRepresenterAPITest::Song"}'
|
154
|
-
end
|
155
|
-
|
156
|
-
class ShowContract < Show
|
157
|
-
def represented
|
158
|
-
"Object"
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
it "can be overriden to use the contract" do
|
163
|
-
ShowContract.({}).to_json.must_equal %{{"class":"String"}}
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
describe "#to_json" do
|
168
|
-
class OptionsShow < Trailblazer::Operation
|
169
|
-
include Representer
|
170
|
-
|
171
|
-
representer do
|
172
|
-
property :class
|
173
|
-
property :id
|
174
|
-
end
|
175
|
-
|
176
|
-
def to_json(*)
|
177
|
-
super(self["params"])
|
178
|
-
end
|
179
|
-
|
180
|
-
include Model::Builder
|
181
|
-
def model!(*)
|
182
|
-
Song.new(1)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
it "allows to pass options to #to_json" do
|
187
|
-
OptionsShow.(include: [:id]).to_json.must_equal %{{"id":1}}
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
class DifferentParseAndRenderingRepresenterTest < MiniTest::Spec
|
193
|
-
Album = Struct.new(:title)
|
194
|
-
|
195
|
-
# rendering
|
196
|
-
class Create < Trailblazer::Operation
|
197
|
-
include Contract::Explicit
|
198
|
-
extend Representer::DSL
|
199
|
-
include Representer::Rendering # no Deserializer::Hash here or anything.
|
200
|
-
|
201
|
-
contract do
|
202
|
-
property :title
|
203
|
-
end
|
204
|
-
|
205
|
-
representer do
|
206
|
-
property :title, as: :Title
|
207
|
-
end
|
208
|
-
|
209
|
-
def call(params)
|
210
|
-
self["model"] = Album.new
|
211
|
-
validate(params) do
|
212
|
-
contract.sync
|
213
|
-
end
|
214
|
-
self
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
it do
|
219
|
-
Create.(title: "The Kids").to_json.must_equal %{{"Title":"The Kids"}}
|
220
|
-
end
|
221
|
-
|
222
|
-
# parsing
|
223
|
-
class Update < Trailblazer::Operation
|
224
|
-
include Contract::Explicit
|
225
|
-
extend Representer::DSL
|
226
|
-
include Representer::Deserializer::Hash # no Rendering.
|
227
|
-
|
228
|
-
representer do
|
229
|
-
property :title, as: :Title
|
230
|
-
end
|
231
|
-
|
232
|
-
contract do
|
233
|
-
property :title
|
234
|
-
end
|
235
|
-
|
236
|
-
def call(params)
|
237
|
-
self["model"] = Album.new
|
238
|
-
|
239
|
-
validate(params) do
|
240
|
-
contract.sync
|
241
|
-
end
|
242
|
-
|
243
|
-
self
|
244
|
-
end
|
245
|
-
|
246
|
-
def to_json(*)
|
247
|
-
%{{"title": "#{self["model"].title}"}}
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
it do
|
252
|
-
Update.("Title" => "The Kids").to_json.must_equal %{{"title": "The Kids"}}
|
253
|
-
end
|
254
|
-
end
|