trailblazer 1.1.2 → 2.0.0.beta1

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