trailblazer 0.3.3 → 1.0.0.rc1
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 +4 -4
- data/.travis.yml +1 -1
- data/CHANGES.md +12 -0
- data/Gemfile +2 -1
- data/README.md +73 -38
- data/Rakefile +1 -1
- data/lib/trailblazer/autoloading.rb +4 -1
- data/lib/trailblazer/endpoint.rb +7 -13
- data/lib/trailblazer/operation.rb +54 -40
- data/lib/trailblazer/operation/builder.rb +26 -0
- data/lib/trailblazer/operation/collection.rb +1 -2
- data/lib/trailblazer/operation/controller.rb +36 -48
- data/lib/trailblazer/operation/dispatch.rb +11 -11
- data/lib/trailblazer/operation/model.rb +50 -0
- data/lib/trailblazer/operation/model/dsl.rb +29 -0
- data/lib/trailblazer/operation/model/external.rb +34 -0
- data/lib/trailblazer/operation/policy.rb +87 -0
- data/lib/trailblazer/operation/policy/guard.rb +34 -0
- data/lib/trailblazer/operation/representer.rb +33 -12
- data/lib/trailblazer/operation/resolver.rb +30 -0
- data/lib/trailblazer/operation/responder.rb +0 -1
- data/lib/trailblazer/operation/worker.rb +24 -7
- data/lib/trailblazer/version.rb +1 -1
- data/test/collection_test.rb +2 -1
- data/test/{crud_test.rb → model_test.rb} +17 -35
- data/test/operation/builder_test.rb +41 -0
- data/test/operation/dsl/callback_test.rb +108 -0
- data/test/operation/dsl/contract_test.rb +104 -0
- data/test/operation/dsl/representer_test.rb +143 -0
- data/test/operation/external_model_test.rb +71 -0
- data/test/operation/guard_test.rb +97 -0
- data/test/operation/policy_test.rb +97 -0
- data/test/operation/resolver_test.rb +83 -0
- data/test/operation_test.rb +7 -75
- data/test/rails/__respond_test.rb +20 -0
- data/test/rails/controller_test.rb +4 -102
- data/test/rails/endpoint_test.rb +7 -47
- data/test/rails/fake_app/controllers.rb +16 -21
- data/test/rails/fake_app/rails_app.rb +5 -0
- data/test/rails/fake_app/song/operations.rb +11 -4
- data/test/rails/respond_test.rb +95 -0
- data/test/responder_test.rb +6 -6
- data/test/rollback_test.rb +2 -2
- data/test/worker_test.rb +13 -9
- data/trailblazer.gemspec +2 -2
- metadata +38 -15
- data/lib/trailblazer/operation/crud.rb +0 -82
- data/lib/trailblazer/rails/railtie.rb +0 -34
- data/test/rails/fake_app/views/bands/show.html.erb +0 -1
data/test/operation_test.rb
CHANGED
@@ -201,14 +201,14 @@ class OperationTest < MiniTest::Spec
|
|
201
201
|
|
202
202
|
|
203
203
|
# unlimited arguments for ::run and friends.
|
204
|
-
class OperationReceivingLottaArguments < Trailblazer::Operation
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
end
|
204
|
+
# class OperationReceivingLottaArguments < Trailblazer::Operation
|
205
|
+
# def process(model, params)
|
206
|
+
# @model = [model, params]
|
207
|
+
# end
|
208
|
+
# include Inspect
|
209
|
+
# end
|
210
210
|
|
211
|
-
it { OperationReceivingLottaArguments.run(Object, {}).to_s.must_equal %{[true, <OperationReceivingLottaArguments @model=[Object, {}]>]} }
|
211
|
+
# it { OperationReceivingLottaArguments.run(Object, {}).to_s.must_equal %{[true, <OperationReceivingLottaArguments @model=[Object, {}]>]} }
|
212
212
|
|
213
213
|
# ::present only runs #setup! which runs #model!.
|
214
214
|
class ContractOnlyOperation < Trailblazer::Operation
|
@@ -233,74 +233,6 @@ class OperationTest < MiniTest::Spec
|
|
233
233
|
end
|
234
234
|
|
235
235
|
|
236
|
-
class OperationBuilderTest < MiniTest::Spec
|
237
|
-
class ParentOperation < Trailblazer::Operation
|
238
|
-
def process(params)
|
239
|
-
end
|
240
|
-
|
241
|
-
class Sub < self
|
242
|
-
end
|
243
|
-
|
244
|
-
builds do |params|
|
245
|
-
Sub if params[:sub]
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
it { ParentOperation.run({}).last.class.must_equal ParentOperation }
|
250
|
-
it { ParentOperation.run({sub: true}).last.class.must_equal ParentOperation::Sub }
|
251
|
-
it { ParentOperation.({}).class.must_equal ParentOperation }
|
252
|
-
it { ParentOperation.({sub: true}).class.must_equal ParentOperation::Sub }
|
253
|
-
end
|
254
|
-
|
255
|
-
|
256
|
-
# ::contract builds Reform::Form class
|
257
|
-
class OperationInheritanceTest < MiniTest::Spec
|
258
|
-
class Operation < Trailblazer::Operation
|
259
|
-
contract do
|
260
|
-
property :title
|
261
|
-
property :band
|
262
|
-
|
263
|
-
# TODO/DISCUSS: this is needed in order to "handle" the anon forms. but in Trb, that
|
264
|
-
# doesn't really matter as AM is automatically included?
|
265
|
-
def self.name
|
266
|
-
"Song"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
class JSON < self
|
271
|
-
# inherit Contract
|
272
|
-
contract do
|
273
|
-
property :genre, validates: {presence: true}
|
274
|
-
property :band, virtual: true
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# inherits subclassed Contract.
|
280
|
-
it { Operation.contract_class.wont_equal Operation::JSON.contract_class }
|
281
|
-
|
282
|
-
it do
|
283
|
-
form = Operation.contract_class.new(OpenStruct.new)
|
284
|
-
form.validate({})#.must_equal true
|
285
|
-
form.errors.to_s.must_equal "{}"
|
286
|
-
|
287
|
-
form = Operation::JSON.contract_class.new(OpenStruct.new)
|
288
|
-
form.validate({})#.must_equal true
|
289
|
-
form.errors.to_s.must_equal "{:genre=>[\"can't be blank\"]}"
|
290
|
-
end
|
291
|
-
|
292
|
-
# allows overriding options
|
293
|
-
it do
|
294
|
-
form = Operation::JSON.contract_class.new(song = OpenStruct.new)
|
295
|
-
form.validate({genre: "Punkrock", band: "Osker"}).must_equal true
|
296
|
-
form.sync
|
297
|
-
|
298
|
-
song.genre.must_equal "Punkrock"
|
299
|
-
song.band.must_equal nil
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
|
304
236
|
class OperationErrorsTest < MiniTest::Spec
|
305
237
|
class Operation < Trailblazer::Operation
|
306
238
|
contract do
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "rack/test"
|
3
|
+
|
4
|
+
class Respond___Test < MiniTest::Spec
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
Rails.application
|
9
|
+
end
|
10
|
+
|
11
|
+
it "respond with :namespace" do
|
12
|
+
# Rails.logger = Logger.new(STDOUT) # TODO: how do we get exceptions with rack-test?
|
13
|
+
|
14
|
+
post "/songs/create_with_namespace", {title: "Teenager Liebe"}.to_json, "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT"=>"application/json"
|
15
|
+
# puts last_response.inspect
|
16
|
+
last_response.status.must_equal 201
|
17
|
+
id = Song.last.id
|
18
|
+
last_response.headers["Location"].must_equal "http://example.org/api/songs/#{id}"
|
19
|
+
end
|
20
|
+
end
|
@@ -42,99 +42,7 @@ class ProcessParamsTest < ActionController::TestCase
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
class ResponderRespondTest < ActionController::TestCase
|
46
|
-
tests SongsController
|
47
|
-
|
48
|
-
# HTML
|
49
|
-
# #respond Create [valid]
|
50
|
-
test "Create [html/valid]" do
|
51
|
-
post :create, {song: {title: "You're Going Down"}}
|
52
|
-
assert_redirected_to song_path(Song.last)
|
53
|
-
end
|
54
|
-
|
55
|
-
test "Create [html/valid/location]" do
|
56
|
-
post :other_create, {song: {title: "You're Going Down"}}
|
57
|
-
assert_redirected_to other_create_songs_path
|
58
|
-
end
|
59
|
-
|
60
|
-
test "Create [html/invalid]" do
|
61
|
-
post :create, {song: {title: ""}}
|
62
|
-
assert_response 200
|
63
|
-
assert_equal @response.body, "{:title=>["can't be blank"]}"
|
64
|
-
end
|
65
|
-
|
66
|
-
test "Create [html/invalid/action]" do
|
67
|
-
post :other_create, {song: {title: ""}}
|
68
|
-
assert_response 200
|
69
|
-
assert_equal @response.body, "OTHER SONG\n{:title=>["can't be blank"]}\n"
|
70
|
-
assert_template "songs/another_view"
|
71
|
-
end
|
72
|
-
|
73
|
-
test "Delete [html/valid]" do
|
74
|
-
song = Song::Create[song: {title: "You're Going Down"}].model
|
75
|
-
delete :destroy, id: song.id
|
76
|
-
assert_redirected_to songs_path
|
77
|
-
# assert that model is deleted.
|
78
|
-
end
|
79
|
-
|
80
|
-
test "respond with block [html/valid]" do
|
81
|
-
post :create_with_block, {song: {title: "You're Going Down"}}
|
82
|
-
assert_response 200
|
83
|
-
assert_equal "block run, valid: true", response.body
|
84
|
-
end
|
85
|
-
|
86
|
-
test "respond with block [html/invalid]" do
|
87
|
-
post :create_with_block, {song: {title: ""}}
|
88
|
-
assert_response 200
|
89
|
-
assert_equal "block run, valid: false", response.body
|
90
|
-
end
|
91
|
-
|
92
|
-
# JSON
|
93
|
-
test "Delete [json/valid]" do
|
94
|
-
song = Song::Create[song: {title: "You're Going Down"}].model
|
95
|
-
delete :destroy, id: song.id, format: :json
|
96
|
-
assert_response 204 # no content.
|
97
|
-
end
|
98
|
-
|
99
|
-
# JS
|
100
|
-
test "Delete [js/valid]" do
|
101
|
-
song = Song::Create[song: {title: "You're Going Down"}].model
|
102
|
-
assert_raises ActionView::MissingTemplate do
|
103
|
-
# js wants to render destroy.js.erb
|
104
|
-
delete :destroy, id: song.id, format: :js
|
105
|
-
end
|
106
|
-
end
|
107
45
|
|
108
|
-
test "Delete with formats [js/valid]" do
|
109
|
-
song = Song::Create[song: {title: "You're Going Down"}].model
|
110
|
-
|
111
|
-
delete :destroy_with_formats, id: song.id, format: :js
|
112
|
-
assert_response 200
|
113
|
-
assert_equal "Song slayer!", response.body
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
class ResponderRespondWithJSONTest < ActionController::TestCase
|
118
|
-
tests BandsController
|
119
|
-
|
120
|
-
# JSON
|
121
|
-
test "Create [JSON/valid]" do
|
122
|
-
post :create, {name: "SNFU"}.to_json, format: :json
|
123
|
-
assert_response 201
|
124
|
-
assert_equal "SNFU", Band.last.name
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# TODO: merge with above tests on SongsController.
|
129
|
-
class ControllerRespondTest < ActionController::TestCase
|
130
|
-
tests BandsController
|
131
|
-
|
132
|
-
test "#respond with builds" do
|
133
|
-
post :create, band: {name: "SNFU"}, admin: true
|
134
|
-
assert_response 302
|
135
|
-
assert_equal "SNFU [ADMIN]", Band.last.name
|
136
|
-
end
|
137
|
-
end
|
138
46
|
|
139
47
|
class ResponderRunTest < ActionController::TestCase
|
140
48
|
tests BandsController
|
@@ -170,7 +78,7 @@ class ControllerPresentTest < ActionController::TestCase
|
|
170
78
|
|
171
79
|
get :show, id: band.id
|
172
80
|
|
173
|
-
assert_equal "bands/show
|
81
|
+
assert_equal "bands/show: Band,Band,true,Band::Update,Essen,nil", response.body
|
174
82
|
end
|
175
83
|
|
176
84
|
# TODO: this implicitely tests builds. maybe have separate test for that?
|
@@ -206,21 +114,15 @@ class ControllerFormTest < ActionController::TestCase
|
|
206
114
|
|
207
115
|
test "#form" do
|
208
116
|
get :new
|
209
|
-
|
210
117
|
assert_select "form input#band_name"
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
test "#form with block" do
|
215
|
-
get :new_with_block
|
216
|
-
|
217
|
-
assert_select "b", "Band,Band,true,Band::Create,Essen"
|
118
|
+
assert response.body =~ /<a>Sydney<\/a>/ # prepopulate!
|
119
|
+
assert_select "b", "Band,true,Band::Create,Band::Create,Essen"
|
218
120
|
end
|
219
121
|
|
220
122
|
test "#form with builder" do
|
221
123
|
get :new, admin: true
|
222
124
|
|
223
|
-
assert_select "b", "
|
125
|
+
assert_select "b", "Band,true,Band::Create::Admin,Band::Create::Admin,Essen"
|
224
126
|
end
|
225
127
|
end
|
226
128
|
|
data/test/rails/endpoint_test.rb
CHANGED
@@ -1,47 +1,9 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
3
|
module RailsEndpoint
|
4
|
-
# this tests "the rails way" where both JSON and HTML operation use the pre-parsed params hash.
|
5
|
-
class UnconfiguredTest < ActionDispatch::IntegrationTest
|
6
|
-
class Create < Trailblazer::Operation
|
7
|
-
include CRUD
|
8
|
-
model Band
|
9
|
-
|
10
|
-
def process(params)
|
11
|
-
@model = Band.create(params["band"].permit(:name)) # how ridiculous is strong_parameters?
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class BandsController < ApplicationController
|
16
|
-
include Trailblazer::Operation::Controller
|
17
|
-
respond_to :html, :json
|
18
|
-
# missing document_formats.
|
19
|
-
|
20
|
-
def create
|
21
|
-
run Create if request.format == :json
|
22
|
-
run Create if request.format == :html
|
23
|
-
render text: ""
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
test "Create" do
|
28
|
-
post "/rails_endpoint/unconfigured_test/bands", {band: {name: "SNFU"}}
|
29
|
-
assert_response 200
|
30
|
-
assert_equal "SNFU", Band.last.name
|
31
|
-
end
|
32
|
-
|
33
|
-
test "Create: JSON" do
|
34
|
-
headers = { 'CONTENT_TYPE' => 'application/json' } # hahaha, oh god, rails, good bye!
|
35
|
-
post "/rails_endpoint/unconfigured_test/bands", {band: {name: "Strike Anywhere"}}.to_json, headers
|
36
|
-
assert_response 200
|
37
|
-
assert_equal "Strike Anywhere", Band.last.name
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
4
|
class ConfiguredTest < ActionDispatch::IntegrationTest
|
43
5
|
class Create < Trailblazer::Operation
|
44
|
-
include
|
6
|
+
include Model
|
45
7
|
model Band
|
46
8
|
|
47
9
|
def process(params)
|
@@ -49,20 +11,15 @@ module RailsEndpoint
|
|
49
11
|
end
|
50
12
|
end
|
51
13
|
|
52
|
-
class JSONCreate <
|
53
|
-
include CRUD
|
54
|
-
model Band
|
55
|
-
|
14
|
+
class JSONCreate < Create
|
56
15
|
def process(params)
|
57
16
|
@model = Band.create(JSON.parse(params["band"])) # document comes in keyed as "band".
|
58
17
|
end
|
59
|
-
|
60
18
|
end
|
61
19
|
|
62
20
|
class BandsController < ApplicationController
|
63
21
|
include Trailblazer::Operation::Controller
|
64
22
|
respond_to :html, :json
|
65
|
-
operation document_formats: :json
|
66
23
|
|
67
24
|
def create
|
68
25
|
run Create if request.format == :html
|
@@ -78,9 +35,12 @@ module RailsEndpoint
|
|
78
35
|
end
|
79
36
|
|
80
37
|
test "Create: JSON" do
|
81
|
-
post "/rails_endpoint/configured_test/bands.json", {name: "NOFX"}.to_json#, headers # FIXME: headers do not work
|
38
|
+
post "/rails_endpoint/configured_test/bands.json", {name: "NOFX"}.to_json,'CONTENT_TYPE' => 'application/json', "HTTP_ACCEPT"=>"application/json" #, headers # FIXME: headers do not work
|
82
39
|
assert_response 200
|
83
40
|
assert_equal "NOFX", Band.last.name
|
84
41
|
end
|
85
42
|
end
|
86
|
-
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# test :is_document
|
46
|
+
# test :namespace
|
@@ -21,11 +21,11 @@ ERB
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def other_create
|
24
|
-
respond Song::Create,
|
24
|
+
respond Song::Create, location: other_create_songs_path, action: :another_view
|
25
25
|
end
|
26
26
|
|
27
27
|
def create_with_params
|
28
|
-
respond Song::Create, song: {title: "A Beautiful Indifference"}
|
28
|
+
respond Song::Create, {}, song: {title: "A Beautiful Indifference"}
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_with_block
|
@@ -34,6 +34,10 @@ ERB
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def create_with_namespace
|
38
|
+
respond Song::Create::Json, namespace: [:api]
|
39
|
+
end
|
40
|
+
|
37
41
|
def destroy
|
38
42
|
respond Song::Delete
|
39
43
|
end
|
@@ -48,41 +52,32 @@ end
|
|
48
52
|
class BandsController < ApplicationController
|
49
53
|
include Trailblazer::Operation::Controller
|
50
54
|
respond_to :html, :json
|
51
|
-
operation document_formats: :json
|
52
55
|
|
53
56
|
def index
|
54
57
|
collection Band::Index
|
55
58
|
end
|
56
59
|
|
57
60
|
def show
|
58
|
-
present Band::Update
|
59
|
-
|
60
|
-
|
61
|
+
op = present Band::Update
|
62
|
+
@klass = op.model.class
|
63
|
+
@locality = params[:band][:locality] unless params[:format] == "json"
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
return render json: op.to_json if params[:format] == "json"
|
66
|
+
render text: "bands/show: #{[@klass, @model.class, @form.is_a?(Reform::Form), @operation.class, @locality, @form.locality.inspect].join(',')}"
|
64
67
|
end
|
65
68
|
|
66
69
|
def new
|
67
|
-
form Band::Create
|
70
|
+
@op = form Band::Create
|
71
|
+
|
72
|
+
@locality = params[:band][:locality]
|
68
73
|
|
69
74
|
render inline: <<-ERB
|
70
75
|
<%= form_for @form do |f| %>
|
71
76
|
<%= f.text_field :name %>
|
77
|
+
<a><%= @form.locality %></a>
|
72
78
|
<% end %>
|
73
79
|
|
74
|
-
<b><%= [@
|
75
|
-
ERB
|
76
|
-
end
|
77
|
-
|
78
|
-
def new_with_block
|
79
|
-
form Band::Create do |op|
|
80
|
-
@klass = op.model.class
|
81
|
-
@locality = params[:band][:locality]
|
82
|
-
end
|
83
|
-
|
84
|
-
render inline: <<-ERB
|
85
|
-
<b><%= [@klass, @model.class, @form.is_a?(Reform::Form), @operation.class, @locality].join(",") %></b>
|
80
|
+
<b><%= [@model.class, @form.is_a?(Reform::Form), @operation.class, @op.class, @locality].join(",") %></b>
|
86
81
|
ERB
|
87
82
|
end
|
88
83
|
|
@@ -33,6 +33,7 @@ app.routes.draw do
|
|
33
33
|
post :other_create
|
34
34
|
post :create_with_params
|
35
35
|
post :create_with_block
|
36
|
+
post :create_with_namespace
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -58,6 +59,10 @@ app.routes.draw do
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
62
|
+
namespace :api do
|
63
|
+
resources :songs
|
64
|
+
end
|
65
|
+
|
61
66
|
resources :tenants, only: [:show]
|
62
67
|
end
|
63
68
|
|
@@ -2,7 +2,7 @@ require 'trailblazer/autoloading'
|
|
2
2
|
|
3
3
|
class Song < ActiveRecord::Base
|
4
4
|
class Create < Trailblazer::Operation
|
5
|
-
include
|
5
|
+
include Model
|
6
6
|
include Responder
|
7
7
|
model Song, :create
|
8
8
|
|
@@ -17,6 +17,13 @@ class Song < ActiveRecord::Base
|
|
17
17
|
contract.save
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
|
22
|
+
class Json < Create
|
23
|
+
def process(params)
|
24
|
+
@model = Song.create(JSON.parse(params[:song]))
|
25
|
+
end
|
26
|
+
end
|
20
27
|
end
|
21
28
|
|
22
29
|
|
@@ -32,7 +39,7 @@ end
|
|
32
39
|
|
33
40
|
class Band < ActiveRecord::Base
|
34
41
|
class Create < Trailblazer::Operation
|
35
|
-
include
|
42
|
+
include Model, Responder#, Representer
|
36
43
|
model Band, :create
|
37
44
|
|
38
45
|
contract do
|
@@ -40,7 +47,7 @@ class Band < ActiveRecord::Base
|
|
40
47
|
model Band
|
41
48
|
|
42
49
|
property :name, validates: {presence: true}
|
43
|
-
property :locality
|
50
|
+
property :locality, prepopulator: ->(*) { self.locality = "Sydney" }
|
44
51
|
|
45
52
|
# class: Song #=> always create new song
|
46
53
|
# instance: { Song.find(params[:id]) or Song.new } # same as find_or_create ?
|
@@ -130,7 +137,7 @@ end
|
|
130
137
|
|
131
138
|
class Tenant < ActiveRecord::Base
|
132
139
|
class Show < Trailblazer::Operation
|
133
|
-
include
|
140
|
+
include Model
|
134
141
|
model Tenant, :update
|
135
142
|
end
|
136
143
|
end
|