trailblazer 1.1.2 → 2.0.0.beta1

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -7
  3. data/CHANGES.md +108 -0
  4. data/COMM-LICENSE +91 -0
  5. data/Gemfile +18 -4
  6. data/LICENSE.txt +7 -20
  7. data/README.md +55 -15
  8. data/Rakefile +21 -2
  9. data/draft-1.2.rb +7 -0
  10. data/lib/trailblazer.rb +17 -4
  11. data/lib/trailblazer/dsl.rb +47 -0
  12. data/lib/trailblazer/operation/auto_inject.rb +47 -0
  13. data/lib/trailblazer/operation/builder.rb +18 -18
  14. data/lib/trailblazer/operation/callback.rb +31 -38
  15. data/lib/trailblazer/operation/contract.rb +46 -0
  16. data/lib/trailblazer/operation/controller.rb +45 -27
  17. data/lib/trailblazer/operation/guard.rb +24 -0
  18. data/lib/trailblazer/operation/model.rb +41 -33
  19. data/lib/trailblazer/operation/nested.rb +43 -0
  20. data/lib/trailblazer/operation/params.rb +13 -0
  21. data/lib/trailblazer/operation/persist.rb +13 -0
  22. data/lib/trailblazer/operation/policy.rb +26 -72
  23. data/lib/trailblazer/operation/present.rb +19 -0
  24. data/lib/trailblazer/operation/procedural/contract.rb +15 -0
  25. data/lib/trailblazer/operation/procedural/validate.rb +22 -0
  26. data/lib/trailblazer/operation/pundit.rb +42 -0
  27. data/lib/trailblazer/operation/representer.rb +25 -92
  28. data/lib/trailblazer/operation/rescue.rb +23 -0
  29. data/lib/trailblazer/operation/resolver.rb +18 -24
  30. data/lib/trailblazer/operation/validate.rb +50 -0
  31. data/lib/trailblazer/operation/wrap.rb +37 -0
  32. data/lib/trailblazer/version.rb +1 -1
  33. data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
  34. data/test/docs/auto_inject_test.rb +30 -0
  35. data/test/docs/contract_test.rb +429 -0
  36. data/test/docs/dry_test.rb +31 -0
  37. data/test/docs/guard_test.rb +143 -0
  38. data/test/docs/nested_test.rb +117 -0
  39. data/test/docs/policy_test.rb +2 -0
  40. data/test/docs/pundit_test.rb +109 -0
  41. data/test/docs/representer_test.rb +268 -0
  42. data/test/docs/rescue_test.rb +153 -0
  43. data/test/docs/wrap_test.rb +174 -0
  44. data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
  45. data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
  46. data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
  47. data/test/module_test.rb +22 -15
  48. data/test/operation/builder_test.rb +66 -18
  49. data/test/operation/callback_test.rb +70 -0
  50. data/test/operation/contract_test.rb +385 -15
  51. data/test/operation/dsl/callback_test.rb +18 -30
  52. data/test/operation/dsl/contract_test.rb +209 -19
  53. data/test/operation/dsl/representer_test.rb +42 -15
  54. data/test/operation/guard_test.rb +1 -147
  55. data/test/operation/model_test.rb +105 -0
  56. data/test/operation/params_test.rb +36 -0
  57. data/test/operation/persist_test.rb +44 -0
  58. data/test/operation/pipedream_test.rb +59 -0
  59. data/test/operation/pipetree_test.rb +104 -0
  60. data/test/operation/present_test.rb +24 -0
  61. data/test/operation/pundit_test.rb +104 -0
  62. data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
  63. data/test/operation/resolver_test.rb +34 -70
  64. data/test/operation_test.rb +57 -189
  65. data/test/test_helper.rb +23 -3
  66. data/trailblazer.gemspec +8 -7
  67. metadata +91 -59
  68. data/gemfiles/Gemfile.rails.lock +0 -130
  69. data/gemfiles/Gemfile.reform-2.0 +0 -6
  70. data/gemfiles/Gemfile.reform-2.1 +0 -7
  71. data/lib/trailblazer/autoloading.rb +0 -15
  72. data/lib/trailblazer/endpoint.rb +0 -31
  73. data/lib/trailblazer/operation.rb +0 -175
  74. data/lib/trailblazer/operation/collection.rb +0 -6
  75. data/lib/trailblazer/operation/dispatch.rb +0 -3
  76. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  77. data/lib/trailblazer/operation/model/external.rb +0 -34
  78. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  79. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  80. data/test/callback_test.rb +0 -104
  81. data/test/collection_test.rb +0 -57
  82. data/test/model_test.rb +0 -148
  83. data/test/operation/external_model_test.rb +0 -71
  84. data/test/operation/policy_test.rb +0 -97
  85. data/test/operation/reject_test.rb +0 -34
  86. data/test/rollback_test.rb +0 -47
@@ -0,0 +1,31 @@
1
+ require "test_helper"
2
+ require "dry/container"
3
+
4
+ class DryContainerTest < Minitest::Spec
5
+ Song = Struct.new(:id, :title)
6
+
7
+ class MyContract < Reform::Form
8
+ property :title
9
+ validates :title, length: 2..33
10
+ end
11
+
12
+ my_container = Dry::Container.new
13
+ my_container.register("contract.default.class", MyContract)
14
+ # my_container.namespace("contract") do
15
+ # register("create") { Array }
16
+ # end
17
+
18
+ #---
19
+ #- dependency injection
20
+ #- with Dry-container
21
+ class Create < Trailblazer::Operation
22
+ self.| Model( Song, :new )
23
+ self.| Contract::Build()
24
+ self.| Contract::Validate()
25
+ self.| Persist( method: :sync )
26
+ end
27
+ #:key end
28
+
29
+ it { Create.({ title: "A" }, {}, my_container).inspect("model").must_equal %{<Result:false [#<struct DryContainerTest::Song id=nil, title=nil>] >} }
30
+ it { Create.({ title: "Anthony's Song" }, {}, my_container).inspect("model").must_equal %{<Result:true [#<struct DryContainerTest::Song id=nil, title="Anthony's Song">] >} }
31
+ end
@@ -0,0 +1,143 @@
1
+ require "test_helper"
2
+
3
+ #--
4
+ # with proc
5
+ class DocsGuardProcTest < Minitest::Spec
6
+ #:proc
7
+ class Create < Trailblazer::Operation
8
+ step Policy::Guard( ->(options) { options["params"][:pass] } )
9
+ step :process
10
+ #~pipeonly
11
+
12
+ def process(*)
13
+ self["x"] = true
14
+ end
15
+ #~pipeonly end
16
+ end
17
+ #:proc end
18
+
19
+ it { Create.(pass: false)["x"].must_equal nil }
20
+ it { Create.(pass: true)["x"].must_equal true }
21
+
22
+ #- result object, guard
23
+ it { Create.(pass: true)["result.policy.default"].success?.must_equal true }
24
+ it { Create.(pass: false)["result.policy.default"].success?.must_equal false }
25
+
26
+
27
+
28
+
29
+ #---
30
+ #- Guard inheritance
31
+ class New < Create
32
+ end
33
+
34
+ it { New["pipetree"].inspect.must_equal %{[>>operation.new,&policy.default.eval,>process]} }
35
+ end
36
+
37
+ #---
38
+ # with Callable
39
+ class DocsGuardTest < Minitest::Spec
40
+ #:callable
41
+ class MyGuard
42
+ include Uber::Callable
43
+
44
+ def call(options)
45
+ options["params"][:pass]
46
+ end
47
+ end
48
+ #:callable end
49
+
50
+ #:callable-op
51
+ class Create < Trailblazer::Operation
52
+ step Policy::Guard( MyGuard.new )
53
+ step :process
54
+ #~pipe-only
55
+ def process(*); self[:x] = true; end
56
+ #~pipe-only end
57
+ end
58
+ #:callable-op end
59
+
60
+ it { Create.(pass: false)[:x].must_equal nil }
61
+ it { Create.(pass: true)[:x].must_equal true }
62
+ end
63
+
64
+ #---
65
+ # with method
66
+ # class DocsGuardMethodTest < Minitest::Spec
67
+ # #:method
68
+ # class Create < Trailblazer::Operation
69
+ # step Policy::Guard[ : ]
70
+ # step :process
71
+ # #~pipe-only
72
+ # def process(*); self[:x] = true; end
73
+ # #~pipe-only end
74
+ # end
75
+ # #:method end
76
+
77
+ # it { Create.(pass: false)[:x].must_equal nil }
78
+ # it { Create.(pass: true)[:x].must_equal true }
79
+ # end
80
+
81
+ #---
82
+ # with name:
83
+ class DocsGuardNamedTest < Minitest::Spec
84
+ #:name
85
+ class Create < Trailblazer::Operation
86
+ step Policy::Guard( ->(options) { options["current_user"] }, name: :user )
87
+ # ...
88
+ end
89
+ #:name end
90
+
91
+ it { Create.()["result.policy.user"].success?.must_equal false }
92
+ it { Create.({}, "current_user" => Module)["result.policy.user"].success?.must_equal true }
93
+
94
+ it {
95
+ #:name-result
96
+ result = Create.({}, "current_user" => true)
97
+ result["result.policy.user"].success? #=> true
98
+ #:name-result end
99
+ }
100
+ end
101
+
102
+ #---
103
+ # class-level guard
104
+ class DocsGuardClassLevelTest < Minitest::Spec
105
+ #:class-level
106
+ class Create < Trailblazer::Operation
107
+ step Policy::Guard( ->(options) { options["current_user"] == Module } ),
108
+ before: "operation.new"
109
+ #~pipe--only
110
+ step ->(options) { options["x"] = true }
111
+ #~pipe--only end
112
+ end
113
+ #:class-level end
114
+
115
+ it { Create.(); Create["result.policy"].must_equal nil }
116
+ it { Create.({}, "current_user" => Module)["x"].must_equal true }
117
+ it { Create.({} )["x"].must_equal nil }
118
+ end
119
+
120
+ #---
121
+ # dependency injection
122
+ class DocsGuardInjectionTest < Minitest::Spec
123
+ #:di-op
124
+ class Create < Trailblazer::Operation
125
+ step Policy::Guard( ->(options) { options["current_user"] == Module } )
126
+ end
127
+ #:di-op end
128
+
129
+ it { Create.({}, "current_user" => Module).inspect("").must_equal %{<Result:true [nil] >} }
130
+ it {
131
+ result =
132
+ #:di-call
133
+ Create.({},
134
+ "current_user" => Module,
135
+ "policy.default.eval" => Trailblazer::Operation::Policy::Guard.build(->(options) { false })
136
+ )
137
+ #:di-call end
138
+ result.inspect("").must_equal %{<Result:false [nil] >} }
139
+ end
140
+
141
+
142
+ # TODO:
143
+ #policy.default
@@ -0,0 +1,117 @@
1
+ require "test_helper"
2
+
3
+ class DocsNestedOperationTest < Minitest::Spec
4
+ Song = Struct.new(:id, :title) do
5
+ def self.find(id)
6
+ return new(1, "Bristol") if id == 1
7
+ end
8
+ end
9
+
10
+ #---
11
+ #- nested operations
12
+ #:edit
13
+ class Edit < Trailblazer::Operation
14
+ extend Contract::DSL
15
+
16
+ contract do
17
+ property :title
18
+ end
19
+
20
+ step Model( Song, :find )
21
+ step Contract::Build()
22
+ end
23
+ #:edit end
24
+
25
+ # step Nested( Edit ) #, "policy.default" => self["policy.create"]
26
+ #:update
27
+ class Update < Trailblazer::Operation
28
+ step Nested( Edit )
29
+ step Contract::Validate()
30
+ step Persist( method: :sync )
31
+ end
32
+ #:update end
33
+
34
+ puts Update["pipetree"].inspect(style: :rows)
35
+
36
+ #-
37
+ # Edit allows grabbing model and contract
38
+ it do
39
+ #:edit-call
40
+ result = Edit.(id: 1)
41
+
42
+ result["model"] #=> #<Song id=1, title=\"Bristol\">
43
+ result["contract.default"] #=> #<Reform::Form ..>
44
+ #:edit-call end
45
+ result.inspect("model").must_equal %{<Result:true [#<struct DocsNestedOperationTest::Song id=1, title=\"Bristol\">] >}
46
+ result["contract.default"].model.must_equal result["model"]
47
+ end
48
+
49
+ #-
50
+ # Update also allows grabbing model and contract
51
+ it do
52
+ #:update-call
53
+ result = Update.(id: 1, title: "Call It A Night")
54
+
55
+ result["model"] #=> #<Song id=1 , title=\"Call It A Night\">
56
+ result["contract.default"] #=> #<Reform::Form ..>
57
+ #:update-call end
58
+ result.inspect("model").must_equal %{<Result:true [#<struct DocsNestedOperationTest::Song id=1, title=\"Call It A Night\">] >}
59
+ result["contract.default"].model.must_equal result["model"]
60
+ end
61
+
62
+ #-
63
+ # Edit is successful.
64
+ it do
65
+ result = Update.({ id: 1, title: "Miami" }, "current_user" => Module)
66
+ result.inspect("model").must_equal %{<Result:true [#<struct DocsNestedOperationTest::Song id=1, title="Miami">] >}
67
+ end
68
+
69
+ # Edit fails
70
+ it do
71
+ Update.(id: 2).inspect("model").must_equal %{<Result:false [nil] >}
72
+ end
73
+
74
+ #- shared data
75
+ class B < Trailblazer::Operation
76
+ self.> ->(options) { options["can.B.see.it?"] = options["this.should.not.be.visible.in.B"] }
77
+ self.> ->(options) { options["can.B.see.current_user?"] = options["current_user"] }
78
+ self.> ->(options) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
79
+ end
80
+
81
+ class A < Trailblazer::Operation
82
+ self["A.class.data"] = true
83
+
84
+ self.> ->(options) { options["this.should.not.be.visible.in.B"] = true }
85
+ step Nested B
86
+ end
87
+
88
+ # mutual data from A doesn't bleed into B.
89
+ it { A.()["can.B.see.it?"].must_equal nil }
90
+ it { A.()["this.should.not.be.visible.in.B"].must_equal true }
91
+ # runtime dependencies are visible in B.
92
+ it { A.({}, "current_user" => Module)["can.B.see.current_user?"].must_equal Module }
93
+ # class data from A doesn't bleed into B.
94
+ it { A.()["can.B.see.A.class.data?"].must_equal nil }
95
+
96
+
97
+ # cr_result = Create.({}, "result" => result)
98
+ # puts cr_result["model"]
99
+ # puts cr_result["contract.default"]
100
+ end
101
+
102
+ class NestedClassLevelTest < Minitest::Spec
103
+ #:class-level
104
+ class New < Trailblazer::Operation
105
+ step ->(options) { options["class"] = true }, before: "operation.new"
106
+ step ->(options) { options["x"] = true }
107
+ end
108
+
109
+ class Create < Trailblazer::Operation
110
+ step Nested New
111
+ step ->(options) { options["y"] = true }
112
+ end
113
+ #:class-level end
114
+
115
+ it { Create.().inspect("x", "y").must_equal %{<Result:true [true, true] >} }
116
+ it { Create.(); Create["class"].must_equal nil }
117
+ end
@@ -0,0 +1,2 @@
1
+ # add anything to result
2
+ # insert any options into constructor
@@ -0,0 +1,109 @@
1
+ require "test_helper"
2
+
3
+ #:policy
4
+ class MyPolicy
5
+ def initialize(user, model)
6
+ @user, @model = user, model
7
+ end
8
+
9
+ def create?
10
+ @user == Module && @model.id.nil?
11
+ end
12
+ end
13
+ #:policy end
14
+
15
+ #--
16
+ # with policy
17
+ class DocsPunditProcTest < Minitest::Spec
18
+ Song = Struct.new(:id)
19
+
20
+ #:pundit
21
+ class Create < Trailblazer::Operation
22
+ step Model( Song, :new )
23
+ step Policy::Pundit( MyPolicy, :create? )
24
+ # ...
25
+ end
26
+ #:pundit end
27
+
28
+ it { Create.({}, "current_user" => Module).inspect("model").must_equal %{<Result:true [#<struct DocsPunditProcTest::Song id=nil>] >} }
29
+ it { Create.({} ).inspect("model").must_equal %{<Result:false [#<struct DocsPunditProcTest::Song id=nil>] >} }
30
+
31
+ it do
32
+ #:pundit-result
33
+ result = Create.({}, "current_user" => Module)
34
+ result["result.policy.default"].success? #=> true
35
+ result["result.policy.default"]["policy"] #=> #<MyPolicy ...>
36
+ #:pundit-result end
37
+ result["result.policy.default"].success?.must_equal true
38
+ result["result.policy.default"]["policy"].is_a?(MyPolicy).must_equal true
39
+ end
40
+
41
+ #---
42
+ #- Guard inheritance
43
+ class New < Create
44
+ end
45
+
46
+ it { New["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,&policy.default.eval]} }
47
+
48
+ #---
49
+ # dependency injection
50
+ class AnotherPolicy < MyPolicy
51
+ def create?
52
+ true
53
+ end
54
+ end
55
+
56
+ it {
57
+ result =
58
+ #:di-call
59
+ Create.({},
60
+ "current_user" => Module,
61
+ "policy.default.eval" => Trailblazer::Operation::Policy::Pundit.build(AnotherPolicy, :create?)
62
+ )
63
+ #:di-call end
64
+ result.inspect("").must_equal %{<Result:true [nil] >} }
65
+ end
66
+
67
+ #-
68
+ # with name:
69
+ class PunditWithNameTest < Minitest::Spec
70
+ Song = Struct.new(:id)
71
+
72
+ #:name
73
+ class Create < Trailblazer::Operation
74
+ step Model( Song, :new )
75
+ step Policy::Pundit( MyPolicy, :create?, name: "after_model" )
76
+ # ...
77
+ end
78
+ #:name end
79
+
80
+ it {
81
+ #:name-call
82
+ result = Create.({}, "current_user" => Module)
83
+ result["result.policy.after_model"].success? #=> true
84
+ #:name-call end
85
+ result["result.policy.after_model"].success?.must_equal true }
86
+ end
87
+
88
+ #---
89
+ # class-level guard
90
+ # class DocsGuardClassLevelTest < Minitest::Spec
91
+ # #:class-level
92
+ # class Create < Trailblazer::Operation
93
+ # step Policy::Guard[ ->(options) { options["current_user"] == Module } ],
94
+ # before: "operation.new"
95
+ # #~pipe--only
96
+ # step ->(options) { options["x"] = true }
97
+ # #~pipe--only end
98
+ # end
99
+ # #:class-level end
100
+
101
+ # it { Create.(); Create["result.policy"].must_equal nil }
102
+ # it { Create.({}, "current_user" => Module)["x"].must_equal true }
103
+ # it { Create.({} )["x"].must_equal nil }
104
+ # end
105
+
106
+
107
+
108
+ # TODO:
109
+ #policy.default
@@ -0,0 +1,268 @@
1
+ require "test_helper"
2
+ require "representable/json"
3
+
4
+ #---
5
+ # infer
6
+ class DocsRepresenterInferTest < Minitest::Spec
7
+ Song = Struct.new(:id, :title)
8
+
9
+ #:infer
10
+ class Create < Trailblazer::Operation
11
+ class MyContract < Reform::Form
12
+ property :id
13
+ end
14
+
15
+ step Model( Song, :new )
16
+ step Contract::Build( constant: MyContract )
17
+ step Contract::Validate( representer: Representer.infer(MyContract, format: Representable::JSON) )
18
+ step Persist( method: :sync )
19
+ end
20
+ #:infer end
21
+
22
+ let (:json) { MultiJson.dump(id: 1) }
23
+ it { Create.({}, "document" => json).inspect("model").must_equal %{<Result:true [#<struct DocsRepresenterInferTest::Song id=1, title=nil>] >} }
24
+ end
25
+
26
+ #---
27
+ # explicit
28
+ class DocsRepresenterExplicitTest < Minitest::Spec
29
+ Song = Struct.new(:id, :title)
30
+
31
+ #:explicit-rep
32
+ class MyRepresenter < Representable::Decorator
33
+ include Representable::JSON
34
+ property :id
35
+ end
36
+ #:explicit-rep end
37
+
38
+ #:explicit-op
39
+ class Create < Trailblazer::Operation
40
+ class MyContract < Reform::Form
41
+ property :id
42
+ end
43
+
44
+ step Model( Song, :new )
45
+ step Contract::Build( constant: MyContract )
46
+ step Contract::Validate( representer: MyRepresenter ) # :representer
47
+ step Persist( method: :sync )
48
+ end
49
+ #:explicit-op end
50
+
51
+ let (:json) { MultiJson.dump(id: 1) }
52
+ it { Create.({}, "document" => json).inspect("model").must_equal %{<Result:true [#<struct DocsRepresenterExplicitTest::Song id=1, title=nil>] >} }
53
+ it do
54
+ #:explicit-call
55
+ Create.({}, "document" => '{"id": 1}')
56
+ #:explicit-call end
57
+ end
58
+
59
+ #- render
60
+ it do
61
+ #:render
62
+ result = Create.({}, "document" => '{"id": 1}')
63
+ json = result["representer.default.class"].new(result["model"]).to_json
64
+ json #=> '{"id":1}'
65
+ #:render end
66
+ json.must_equal '{"id":1}'
67
+ end
68
+
69
+ #-
70
+ # with dependency injection
71
+ # overriding the JSON representer with an XML one.
72
+ #:di-rep
73
+ require "representable/xml"
74
+
75
+ class MyXMLRepresenter < Representable::Decorator
76
+ include Representable::XML
77
+ property :id
78
+ end
79
+ #:di-rep end
80
+
81
+ let (:xml) { %{<body><id>1</id></body>} }
82
+ it do
83
+ #:di-call
84
+ result = Create.({},
85
+ "document" => '<body><id>1</id></body>',
86
+ "representer.default.class" => MyXMLRepresenter # injection
87
+ )
88
+ #:di-call end
89
+ result.inspect("model").must_equal %{<Result:true [#<struct DocsRepresenterExplicitTest::Song id="1", title=nil>] >}
90
+ end
91
+ end
92
+
93
+ #---
94
+ # dependency injection
95
+ class DocsRepresenterDITest < Minitest::Spec
96
+ Song = Struct.new(:id, :title)
97
+
98
+ class MyRepresenter < Representable::Decorator
99
+ include Representable::JSON
100
+ property :id
101
+ end
102
+
103
+ class Create < Trailblazer::Operation
104
+ class MyContract < Reform::Form
105
+ property :id
106
+ end
107
+
108
+ step Model( Song, :new )
109
+ step Contract::Build( constant: MyContract )
110
+ step Contract::Validate()
111
+ step Persist( method: :sync )
112
+ end
113
+
114
+ let (:json) { MultiJson.dump(id: 1) }
115
+ it { Create.({}, "document" => json,
116
+ "representer.default.class" => MyRepresenter).inspect("model").must_equal %{<Result:true [#<struct DocsRepresenterDITest::Song id=1, title=nil>] >} }
117
+ end
118
+
119
+ #---
120
+ # inline
121
+ class DocsRepresenterInlineTest < Minitest::Spec
122
+ Song = Struct.new(:id, :title)
123
+
124
+ #:inline
125
+ class Create < Trailblazer::Operation
126
+ class MyContract < Reform::Form
127
+ property :id
128
+ end
129
+
130
+ extend Representer::DSL
131
+
132
+ representer do
133
+ property :id
134
+ end
135
+
136
+ step Model( Song, :new )
137
+ step Contract::Build( constant: MyContract )
138
+ step Contract::Validate( representer: self["representer.default.class"] )
139
+ step Persist( method: :sync )
140
+ end
141
+ #:inline end
142
+
143
+ let (:json) { MultiJson.dump(id: 1) }
144
+ it { Create.({}, "document" => json).inspect("model").must_equal %{<Result:true [#<struct DocsRepresenterInlineTest::Song id=1, title=nil>] >} }
145
+ end
146
+
147
+ #---
148
+ # rendering
149
+ class DocsRepresenterManualRenderTest < Minitest::Spec
150
+ Song = Struct.new(:id, :title) do
151
+ def self.find(id)
152
+ new(id)
153
+ end
154
+ end
155
+
156
+ class Show < Trailblazer::Operation
157
+ extend Representer::DSL
158
+ representer do
159
+ property :id
160
+ end
161
+
162
+ step Model( Song, :find )
163
+ end
164
+
165
+ it do
166
+ result =Show.({ id: 1 })
167
+ json = result["representer.default.class"].new(result["model"]).to_json
168
+ json.must_equal %{{"id":1}}
169
+ end
170
+ end
171
+
172
+ #---
173
+ # naming
174
+
175
+ class DocsRepresenterNamingTest < Minitest::Spec
176
+ MyRepresenter = Object
177
+
178
+ #:naming
179
+ class Create < Trailblazer::Operation
180
+ extend Representer::DSL
181
+ representer MyRepresenter
182
+ end
183
+
184
+ Create["representer.default.class"] #=> MyRepresenter
185
+ #:naming end
186
+ it { Create["representer.default.class"].must_be_kind_of MyRepresenter }
187
+ end
188
+
189
+ #---
190
+ # rendering
191
+ require "roar/json/hal"
192
+
193
+ class DocsRepresenterFullExampleTest < Minitest::Spec
194
+ Song = Struct.new(:id, :title) do
195
+ def initialize(*)
196
+ self.id = 1
197
+ end
198
+ end
199
+
200
+ #:errors-rep
201
+ class ErrorsRepresenter < Representable::Decorator
202
+ include Representable::JSON
203
+ collection :errors
204
+ end
205
+ #:errors-rep end
206
+
207
+ #:full
208
+ class Create < Trailblazer::Operation
209
+ extend Contract::DSL
210
+ extend Representer::DSL
211
+
212
+ contract do
213
+ property :title
214
+ validates :title, presence: true
215
+ end
216
+
217
+ representer :parse do
218
+ property :title
219
+ end
220
+
221
+ representer :render do
222
+ include Roar::JSON::HAL
223
+
224
+ property :id
225
+ property :title
226
+ link(:self) { "/songs/#{represented.id}" }
227
+ end
228
+
229
+ representer :errors, ErrorsRepresenter # explicit reference.
230
+
231
+ step Model( Song, :new )
232
+ step Contract::Build()
233
+ step Contract::Validate( representer: self["representer.parse.class"] )
234
+ step Persist( method: :sync )
235
+ end
236
+ #:full end
237
+
238
+ it do
239
+ result =Create.({}, "document" => '{"title": "Tested"}')
240
+
241
+ json = result["representer.render.class"].new(result["model"]).to_json
242
+
243
+ json.must_equal %{{"id":1,"title":"Tested","_links":{"self":{"href":"/songs/1"}}}}
244
+
245
+
246
+ #:full-call
247
+ def create
248
+ result = Create.(params, "document" => request.body.read)
249
+
250
+ if result.success?
251
+ result["representer.render.class"].new(result["model"]).to_json
252
+ else
253
+ result["representer.errors.class"].new(result["result.contract.default"]).to_json
254
+ end
255
+ end
256
+ #:full-call end
257
+ end
258
+
259
+ it do
260
+ result =Create.({}, "document" => '{"title": ""}')
261
+
262
+ if result.failure?
263
+ json = result["representer.errors.class"].new(result["result.contract.default"]).to_json
264
+ end
265
+
266
+ json.must_equal %{{"errors":[["title","can't be blank"]]}}
267
+ end
268
+ end