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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGES.md +12 -0
  4. data/Gemfile +2 -1
  5. data/README.md +73 -38
  6. data/Rakefile +1 -1
  7. data/lib/trailblazer/autoloading.rb +4 -1
  8. data/lib/trailblazer/endpoint.rb +7 -13
  9. data/lib/trailblazer/operation.rb +54 -40
  10. data/lib/trailblazer/operation/builder.rb +26 -0
  11. data/lib/trailblazer/operation/collection.rb +1 -2
  12. data/lib/trailblazer/operation/controller.rb +36 -48
  13. data/lib/trailblazer/operation/dispatch.rb +11 -11
  14. data/lib/trailblazer/operation/model.rb +50 -0
  15. data/lib/trailblazer/operation/model/dsl.rb +29 -0
  16. data/lib/trailblazer/operation/model/external.rb +34 -0
  17. data/lib/trailblazer/operation/policy.rb +87 -0
  18. data/lib/trailblazer/operation/policy/guard.rb +34 -0
  19. data/lib/trailblazer/operation/representer.rb +33 -12
  20. data/lib/trailblazer/operation/resolver.rb +30 -0
  21. data/lib/trailblazer/operation/responder.rb +0 -1
  22. data/lib/trailblazer/operation/worker.rb +24 -7
  23. data/lib/trailblazer/version.rb +1 -1
  24. data/test/collection_test.rb +2 -1
  25. data/test/{crud_test.rb → model_test.rb} +17 -35
  26. data/test/operation/builder_test.rb +41 -0
  27. data/test/operation/dsl/callback_test.rb +108 -0
  28. data/test/operation/dsl/contract_test.rb +104 -0
  29. data/test/operation/dsl/representer_test.rb +143 -0
  30. data/test/operation/external_model_test.rb +71 -0
  31. data/test/operation/guard_test.rb +97 -0
  32. data/test/operation/policy_test.rb +97 -0
  33. data/test/operation/resolver_test.rb +83 -0
  34. data/test/operation_test.rb +7 -75
  35. data/test/rails/__respond_test.rb +20 -0
  36. data/test/rails/controller_test.rb +4 -102
  37. data/test/rails/endpoint_test.rb +7 -47
  38. data/test/rails/fake_app/controllers.rb +16 -21
  39. data/test/rails/fake_app/rails_app.rb +5 -0
  40. data/test/rails/fake_app/song/operations.rb +11 -4
  41. data/test/rails/respond_test.rb +95 -0
  42. data/test/responder_test.rb +6 -6
  43. data/test/rollback_test.rb +2 -2
  44. data/test/worker_test.rb +13 -9
  45. data/trailblazer.gemspec +2 -2
  46. metadata +38 -15
  47. data/lib/trailblazer/operation/crud.rb +0 -82
  48. data/lib/trailblazer/rails/railtie.rb +0 -34
  49. data/test/rails/fake_app/views/bands/show.html.erb +0 -1
@@ -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
- def process(model, params)
206
- @model = [model, params]
207
- end
208
- include Inspect
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=&gt;[&quot;can&#39;t be blank&quot;]}"
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=&gt;[&quot;can&#39;t be blank&quot;]}\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.html: Band,Band,true,Band::Update,Essen\n", response.body
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
- assert_select "b", ",Band,true,Band::Create"
212
- end
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", ",Band,true,Band::Create::Admin"
125
+ assert_select "b", "Band,true,Band::Create::Admin,Band::Create::Admin,Essen"
224
126
  end
225
127
  end
226
128
 
@@ -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 CRUD
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 < Trailblazer::Operation
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, params, { location: other_create_songs_path, action: :another_view }
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 do |op|
59
- @klass = op.model.class
60
- @locality = params[:band][:locality] unless params[:format] == "json"
61
+ op = present Band::Update
62
+ @klass = op.model.class
63
+ @locality = params[:band][:locality] unless params[:format] == "json"
61
64
 
62
- render json: op.to_json if params[:format] == "json"
63
- end # render :show
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><%= [@klass, @model.class, @form.is_a?(Reform::Form), @operation.class].join(",") %></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 CRUD
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 CRUD, Responder#, Representer
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 CRUD
140
+ include Model
134
141
  model Tenant, :update
135
142
  end
136
143
  end