trailblazer 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/README.md +85 -386
- data/lib/trailblazer/autoloading.rb +4 -0
- data/lib/trailblazer/endpoint.rb +1 -1
- data/lib/trailblazer/operation.rb +1 -1
- data/lib/trailblazer/operation/controller.rb +30 -12
- data/lib/trailblazer/operation/representer.rb +1 -0
- data/lib/trailblazer/version.rb +1 -1
- metadata +2 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2618bcea766e5192fadaa575315a4ad04170654
|
4
|
+
data.tar.gz: fe623b65e42ac232f9bc0ec45a723b5fa5da70e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: befbbee9f37e868da73002c26ce09b91f609ee0e1ded07ffc976dc56cd4440cfa23609911939394c48427d7e9a69bcf28839404a2fb411584d6215b11f79f8c2
|
7
|
+
data.tar.gz: e132220e955dc8b0338584b3fc1055d9abacd2100308277829cbf51bfa6b0ecb5bf2a605c7759bb9a36a191ac9985fe514c4a581ede512cd003a74728928c6e6
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 1.0.1
|
2
|
+
|
3
|
+
* Treat `:js` requests as non-document, too.
|
4
|
+
* `Controller#form` now returns the form object and not the operation.
|
5
|
+
* In `Controller`, `#form`, `#present`, `#run` and `#respond` now all have the same API: `run(constant, options)`. If you want to pass a custom params hash, use `run Comment::Create, params: {..}`.
|
6
|
+
|
1
7
|
# 1.0.0
|
2
8
|
|
3
9
|
* All Rails-relevant files are now in the `trailblazer-rails` gem. You have to include it should you be in a Rails environment.
|
data/README.md
CHANGED
@@ -18,13 +18,13 @@ _Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation,
|
|
18
18
|
|
19
19
|
Trailblazer is designed to handle different contexts like user roles by applying [inheritance](#inheritance) between and [composing](#composing) of operations, form objects, policies, representers and callbacks.
|
20
20
|
|
21
|
-
|
21
|
+
Wanna see some code? Jump [right here](#controllers)!
|
22
22
|
|
23
23
|
## Mission
|
24
24
|
|
25
25
|
While _Trailblazer_ offers you abstraction layers for all aspects of Ruby On Rails, it does _not_ missionize you. Wherever you want, you may fall back to the "Rails Way" with fat models, monolithic controllers, global helpers, etc. This is not a bad thing, but allows you to step-wise introduce Trailblazer's encapsulation in your app without having to rewrite it.
|
26
26
|
|
27
|
-
Trailblazer is all about structure. It helps re-organize existing code into smaller components where different concerns are handled in separated classes.
|
27
|
+
Trailblazer is all about structure. It helps re-organize existing code into smaller components where different concerns are handled in separated classes.
|
28
28
|
|
29
29
|
Again, you can pick which layers you want. Trailblazer doesn't impose technical implementations, it offers mature solutions for recurring problems in all types of Rails applications.
|
30
30
|
|
@@ -61,75 +61,80 @@ The opposite is the case: Controller, view and model become lean endpoints for H
|
|
61
61
|
|
62
62
|
## Routing
|
63
63
|
|
64
|
-
Trailblazer uses Rails routing to map URLs to controllers
|
64
|
+
Trailblazer uses Rails routing to map URLs to controllers, because it works.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Rails.application.routes.draw do
|
68
|
+
resources :comments
|
69
|
+
end
|
70
|
+
```
|
65
71
|
|
66
72
|
## Controllers
|
67
73
|
|
68
|
-
Controllers are lean endpoints for HTTP. They
|
74
|
+
Controllers are lean endpoints for HTTP. They do not contain any business logic. Actions immediately dispatch to an operation.
|
69
75
|
|
70
76
|
```ruby
|
71
77
|
class CommentsController < ApplicationController
|
72
78
|
def create
|
73
|
-
Comment::Create.
|
79
|
+
run Comment::Create # Comment::Create is an operation class.
|
74
80
|
end
|
75
81
|
```
|
76
82
|
|
77
|
-
|
83
|
+
The `#run` method invokes the operation. It allows you to run a conditional block of logic if the operation was successful.
|
78
84
|
|
79
85
|
```ruby
|
80
86
|
class CommentsController < ApplicationController
|
81
87
|
def create
|
82
88
|
run Comment::Create do |op|
|
83
|
-
return redirect_to(comment_path op.model) # success
|
89
|
+
return redirect_to(comment_path op.model) # success!
|
84
90
|
end
|
85
91
|
|
86
|
-
render :new # re-render form.
|
92
|
+
render :new # invalid. re-render form.
|
87
93
|
end
|
88
94
|
```
|
89
95
|
|
90
|
-
Again, the controller only
|
96
|
+
Again, the controller only dispatchs to the operation and handles successful/invalid processing on the HTTP level. For instance by redirecting, setting flash messages, or signing in a user.
|
91
97
|
|
92
|
-
|
98
|
+
[Learn more.](http://trailblazerb.org/gems/operation/controller.html)
|
93
99
|
|
94
|
-
|
100
|
+
## Operation
|
95
101
|
|
96
|
-
Operations encapsulate business logic and are the heart of a Trailblazer architecture.
|
102
|
+
Operations encapsulate business logic and are the heart of a Trailblazer architecture.
|
97
103
|
|
98
|
-
|
104
|
+
Operations don't know about HTTP or the environment. You could use an operation in Rails, Lotus, or Roda, it wouldn't know. This makes them an ideal replacement for test factories.
|
99
105
|
|
100
|
-
|
106
|
+
An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form object, models and your business code.
|
101
107
|
|
102
108
|
```ruby
|
103
|
-
class Comment <
|
104
|
-
|
105
|
-
|
106
|
-
# do whatever you feel like.
|
107
|
-
end
|
109
|
+
class Comment::Create < Trailblazer::Operation
|
110
|
+
def process(params)
|
111
|
+
# do whatever you feel like.
|
108
112
|
end
|
109
113
|
end
|
110
114
|
```
|
111
115
|
|
112
116
|
Operations only need to implement `#process` which receives the params from the caller.
|
113
117
|
|
118
|
+
[Learn more.](http://trailblazerb.org/gems/operation)
|
119
|
+
|
114
120
|
## Validations
|
115
121
|
|
116
|
-
|
122
|
+
In Trailblazer, an operation (usually) has a form object which is simply a `Reform::Form` class. All the [API documented in Reform](https://github.com/apotonick/reform) can be applied and used.
|
117
123
|
|
118
124
|
The operation makes use of the form object using the `#validate` method.
|
119
125
|
|
120
126
|
```ruby
|
121
|
-
class Comment <
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
127
|
+
class Comment::Create < Trailblazer::Operation
|
128
|
+
contract do
|
129
|
+
# this is a Reform::Form class!
|
130
|
+
property :body, validates: {presence: true}
|
131
|
+
end
|
126
132
|
|
127
|
-
|
128
|
-
|
133
|
+
def process(params)
|
134
|
+
@model = Comment.new
|
129
135
|
|
130
|
-
|
131
|
-
|
132
|
-
end
|
136
|
+
validate(params[:comment], @model) do |f|
|
137
|
+
f.save
|
133
138
|
end
|
134
139
|
end
|
135
140
|
end
|
@@ -137,59 +142,43 @@ end
|
|
137
142
|
|
138
143
|
The contract (aka _form_) is defined in the `::contract` block. You can implement nested forms, default values, validations, and everything else Reform provides.
|
139
144
|
|
140
|
-
In
|
141
|
-
|
142
|
-
Technically speaking, what really happens in `Operation#validate` is the following.
|
143
|
-
|
144
|
-
```ruby
|
145
|
-
contract_class.new(@model).validate(params[:comment])
|
146
|
-
```
|
147
|
-
|
148
|
-
This is a familiar work-flow from Reform. Validation does _not_ touch the model.
|
145
|
+
In the `#process` method you can define your business logic.
|
149
146
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
If you prefer keeping your forms in separate classes or even files, you're free to do so.
|
154
|
-
|
155
|
-
```ruby
|
156
|
-
class Create < Trailblazer::Operation
|
157
|
-
self.contract_class = MyCommentForm
|
158
|
-
```
|
147
|
+
[Learn more.](http://trailblazerb.org/gems/operation/api.html)
|
159
148
|
|
160
149
|
## Callbacks
|
161
150
|
|
162
151
|
Post-processing logic (also known as _callbacks_) is configured in operations.
|
163
152
|
|
164
|
-
|
153
|
+
Callbacks can be defined in groups. They use the form object's state tracking to find out whether they should be run.
|
165
154
|
|
166
155
|
```ruby
|
167
|
-
class Create < Trailblazer::Operation
|
156
|
+
class Comment::Create < Trailblazer::Operation
|
168
157
|
callback(:after_save) do
|
169
|
-
on_change :
|
170
|
-
|
171
|
-
property :user do
|
172
|
-
on_create :notify_user!
|
173
|
-
end
|
158
|
+
on_change :markdownize_body! # this is only run when the form object has changed.
|
174
159
|
end
|
175
160
|
```
|
176
161
|
|
177
|
-
|
162
|
+
Callbacks are never triggered automatically, you have to invoke them! This is called _Imperative Callback_.
|
178
163
|
|
179
164
|
```ruby
|
180
|
-
class Create < Trailblazer::Operation
|
181
|
-
|
165
|
+
class Comment::Create < Trailblazer::Operation
|
182
166
|
def process(params)
|
183
167
|
validate(params) do
|
184
168
|
contract.save
|
185
|
-
dispatch!(:after_save)
|
169
|
+
dispatch!(:after_save) # run markdownize_body!, but only if form changed.
|
186
170
|
end
|
187
171
|
end
|
172
|
+
|
173
|
+
def markdownize_body!(comment)
|
174
|
+
comment.body = Markdownize.(comment.body)
|
175
|
+
end
|
188
176
|
end
|
189
177
|
```
|
190
178
|
|
191
179
|
No magical triggering of unwanted logic anymore, but explicit invocations where you want it.
|
192
180
|
|
181
|
+
[Learn more.](http://trailblazerb.org/gems/operation/callback.html)
|
193
182
|
|
194
183
|
## Models
|
195
184
|
|
@@ -198,10 +187,8 @@ Models for persistence can be implemented using any ORM you fancy, for instance
|
|
198
187
|
In Trailblazer, models are completely empty. They solely contain associations and finders. No business logic is allowed in models.
|
199
188
|
|
200
189
|
```ruby
|
201
|
-
class
|
202
|
-
|
203
|
-
has_many :users, through: :authorships
|
204
|
-
has_many :authorships
|
190
|
+
class Comment < ActiveRecord::Base
|
191
|
+
belongs_to :thing
|
205
192
|
|
206
193
|
scope :latest, lambda { all.limit(9).order("id DESC") }
|
207
194
|
end
|
@@ -211,14 +198,12 @@ Only operations and views/cells can access models directly.
|
|
211
198
|
|
212
199
|
## Policies
|
213
200
|
|
214
|
-
The full documentation for [Policy is here](http://trailblazerb.org/gems/operation/policy.html).
|
215
|
-
|
216
201
|
You can abort running an operation using a policy. "[Pundit](https://github.com/elabs/pundit)-style" policy classes define the rules.
|
217
202
|
|
218
203
|
```ruby
|
219
|
-
class
|
220
|
-
def initialize(user,
|
221
|
-
@user, @
|
204
|
+
class Comment::Policy
|
205
|
+
def initialize(user, comment)
|
206
|
+
@user, @comment = user, comment
|
222
207
|
end
|
223
208
|
|
224
209
|
def create?
|
@@ -230,31 +215,15 @@ end
|
|
230
215
|
The rule is enabled via the `::policy` call.
|
231
216
|
|
232
217
|
```ruby
|
233
|
-
class
|
218
|
+
class Comment::Create < Trailblazer::Operation
|
234
219
|
include Policy
|
235
220
|
|
236
|
-
policy
|
221
|
+
policy Comment::Policy, :create?
|
237
222
|
```
|
238
223
|
|
239
224
|
The policy is evaluated in `#setup!`, raises an exception if `false` and suppresses running `#process`.
|
240
225
|
|
241
|
-
|
242
|
-
Thing::Create.(current_user: User.find(1), thing: {}) # raises exception.
|
243
|
-
```
|
244
|
-
|
245
|
-
You can query the `policy` object at any point in your operation without raising an exception.
|
246
|
-
|
247
|
-
To [use policies in your builders](http://trailblazerb.org/gems/operation/builder#resolver.html), please read the documentation.
|
248
|
-
|
249
|
-
```ruby
|
250
|
-
class Thing::Create < Trailblazer::Operation
|
251
|
-
include Resolver
|
252
|
-
|
253
|
-
builder-> (model, policy, params) do
|
254
|
-
return Admin if policy.admin?
|
255
|
-
return SignedIn if params[:current_user]
|
256
|
-
end
|
257
|
-
```
|
226
|
+
[Learn more.](http://trailblazerb.org/gems/operation/policy.html)
|
258
227
|
|
259
228
|
## Views
|
260
229
|
|
@@ -262,304 +231,63 @@ View rendering can happen using the controller as known from Rails. This is abso
|
|
262
231
|
|
263
232
|
More complex UI logic happens in _View Models_ as found in [Cells](https://github.com/apotonick/cells). View models also replace helpers.
|
264
233
|
|
265
|
-
|
266
|
-
## Representers
|
267
|
-
|
268
|
-
Operations can use representers from [Roar](https://github.com/apotonick/roar) to serialize and parse JSON and XML documents for APIs.
|
269
|
-
|
270
|
-
Representers can be inferred automatically from your contract, then may be refined, e.g. with hypermedia or a format like `JSON-API`.
|
271
|
-
|
272
|
-
```ruby
|
273
|
-
class Create < Trailblazer::Operation
|
274
|
-
|
275
|
-
representer do
|
276
|
-
# inherited :body
|
277
|
-
include Roar::JSON::HAL
|
278
|
-
|
279
|
-
link(:self) { comment_path(represented.id) }
|
280
|
-
end
|
281
|
-
```
|
282
|
-
|
283
|
-
The operation can then parse incoming JSON documents in `validate` and render a document via `to_json`. The full [documentation is here](http://trailblazerb.org/gems/operation/representer.html) or in the [Trailblazer book](https://leanpub.com/trailblazer), chapter _Hypermedia APIs_.
|
284
|
-
|
285
|
-
## Tests
|
286
|
-
|
287
|
-
Subject to tests are mainly _Operation_s and _View Model_s, as they encapsulate endpoint behavior of your app. As a nice side effect, factories are replaced by simple _Operation_ calls.
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
## Overview
|
292
|
-
|
293
|
-
Trailblazer is a collection of mature gems that have been developed over the past 10 years and are used in thousands of production apps.
|
294
|
-
|
295
|
-
Using the different layers is completely optional and up to you: Both Cells and Reform can be excluded from your stack if you wish so.
|
296
|
-
|
297
|
-
## Controller API
|
298
|
-
|
299
|
-
[Learn more](http://trailblazerb.org/gems/operation/controller.html)
|
300
|
-
|
301
|
-
Trailblazer provides four methods to present and invoke operations. But before that, you need to include the `Controller` module.
|
234
|
+
The operation's form object can be rendered in views, too.
|
302
235
|
|
303
236
|
```ruby
|
304
237
|
class CommentsController < ApplicationController
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
### Running an operation
|
309
|
-
|
310
|
-
If you do not intend to maintain different request formats, the easiest is to use `#run` to process incoming data using an operation.
|
311
|
-
|
312
|
-
```ruby
|
313
|
-
def create
|
314
|
-
run Comment::Create
|
315
|
-
end
|
316
|
-
```
|
317
|
-
|
318
|
-
This will simply run `Comment::Create[params]`.
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
----
|
325
|
-
|
326
|
-
It's up to the operation's builder to decide which class to instantiate.
|
327
|
-
|
328
|
-
```ruby
|
329
|
-
class Create < Trailblazer::Operation
|
330
|
-
builds do |params|
|
331
|
-
JSON if params[:format] == "json"
|
238
|
+
def new
|
239
|
+
form Comment::Create # will assign the form object to @form.
|
332
240
|
end
|
333
|
-
end
|
334
241
|
```
|
335
242
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
## Operation API
|
243
|
+
Since Reform objects can be passed to form builders, you can use the operation to render and process the form!
|
340
244
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
```ruby
|
346
|
-
op = Comment::Create.(params)
|
245
|
+
```haml
|
246
|
+
= simple_form_for @form do |f|
|
247
|
+
= f.input :body
|
347
248
|
```
|
348
249
|
|
349
|
-
The call style runs the operation and return the operation instance, only.
|
350
|
-
|
351
|
-
In case of an invalid operation, this will raise an exception.
|
352
|
-
|
353
|
-
### Run style
|
354
|
-
|
355
|
-
The _run style_ will do the same as call, but won't raise an exception in case of an invalid result. Instead, it returns result _and_ the operation instance.
|
356
|
-
|
357
|
-
```ruby
|
358
|
-
result, op = Comment::Create.run(params)
|
359
|
-
```
|
360
|
-
|
361
|
-
Additionally, it accepts a block that's only run for a valid state.
|
362
|
-
|
363
|
-
```ruby
|
364
|
-
Comment::Create.run(params) do |op|
|
365
|
-
# only run when valid.
|
366
|
-
end
|
367
|
-
```
|
368
|
-
|
369
|
-
### Inheritance
|
370
|
-
|
371
|
-
Operations fully support inheritance and will copy the contract, the callback groups and any methods from the original operation class.
|
372
|
-
|
373
|
-
```ruby
|
374
|
-
class Create < Trailblazer::Operation
|
375
|
-
contract do
|
376
|
-
property :title
|
377
|
-
end
|
378
250
|
|
379
|
-
|
380
|
-
on_change :notify!
|
381
|
-
end
|
382
|
-
|
383
|
-
def notify!(*)
|
384
|
-
end
|
385
|
-
end
|
386
|
-
```
|
387
|
-
|
388
|
-
This happens with normal Ruby inheritance.
|
389
|
-
|
390
|
-
```ruby
|
391
|
-
class Update < Create
|
392
|
-
# inherited:
|
393
|
-
# contract
|
394
|
-
# callback(:before_save)
|
395
|
-
# def notify!(*)
|
396
|
-
end
|
397
|
-
```
|
398
|
-
|
399
|
-
You can customize callback groups and contracts using the `:inherit` option, add and remove properties or add methods.
|
400
|
-
|
401
|
-
[Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
|
402
|
-
|
403
|
-
### Modules
|
404
|
-
|
405
|
-
In case inheritance is not enough for you, use modules to share common functionality.
|
406
|
-
|
407
|
-
```ruby
|
408
|
-
module ExtendedCreate
|
409
|
-
include Trailblazer::Operation::Module
|
410
|
-
|
411
|
-
contract do
|
412
|
-
property :id
|
413
|
-
end
|
414
|
-
|
415
|
-
callback do
|
416
|
-
on_update :update!
|
417
|
-
end
|
418
|
-
|
419
|
-
def update!(song)
|
420
|
-
# do something
|
421
|
-
end
|
422
|
-
end
|
423
|
-
```
|
424
|
-
|
425
|
-
Modules can be included and will simply run the declarations in the including class.
|
426
|
-
|
427
|
-
```ruby
|
428
|
-
class Create::Admin < Create
|
429
|
-
include ExtendedCreate
|
430
|
-
|
431
|
-
# contract has :title and :id now.
|
432
|
-
```
|
433
|
-
|
434
|
-
Modules are often used to modify an existing operation for admin or signed-in roles.
|
435
|
-
|
436
|
-
[Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
|
437
|
-
|
438
|
-
|
439
|
-
## Form API
|
440
|
-
|
441
|
-
Usually, an operation has a form object.
|
442
|
-
|
443
|
-
```ruby
|
444
|
-
class Create < Trailblazer::Operation
|
445
|
-
contract do
|
446
|
-
property :body
|
447
|
-
validates :body: presence: true, length: {max: 160}
|
448
|
-
end
|
449
|
-
```
|
450
|
-
|
451
|
-
A `::contract` block simply opens a new Reform class for you.
|
452
|
-
|
453
|
-
```ruby
|
454
|
-
contract do #=> Class.new(Reform::Form) do
|
455
|
-
```
|
456
|
-
|
457
|
-
This allows using Reform's API in the block.
|
458
|
-
|
459
|
-
When inheriting, the block is `class_eval`ed in the inherited class' context and allows adding, removing and customizing the sub contract.
|
460
|
-
|
461
|
-
### CRUD Semantics
|
462
|
-
|
463
|
-
You can make Trailblazer find and create models for you using the `Model` module.
|
464
|
-
|
465
|
-
```ruby
|
466
|
-
require 'trailblazer/operation/model'
|
467
|
-
|
468
|
-
class Comment < ActiveRecord::Base
|
469
|
-
class Create < Trailblazer::Operation
|
470
|
-
include Model
|
471
|
-
model Comment, :create
|
472
|
-
|
473
|
-
contract do
|
474
|
-
# ..
|
475
|
-
end
|
476
|
-
|
477
|
-
def process(params)
|
478
|
-
validate(params[:comment]) do |f|
|
479
|
-
f.save
|
480
|
-
end
|
481
|
-
end
|
482
|
-
end
|
483
|
-
end
|
484
|
-
```
|
485
|
-
|
486
|
-
You have to tell `Model` the model class and what action to implement using `::model`.
|
487
|
-
|
488
|
-
Note how you do not have to pass the `@model` to validate anymore. Also, the `@model` gets created automatically and is accessible using `Operation#model`.
|
489
|
-
|
490
|
-
In inherited operations, you can override the action, only, using `::action`.
|
491
|
-
|
492
|
-
```ruby
|
493
|
-
class Update < Create
|
494
|
-
action :update
|
495
|
-
end
|
496
|
-
```
|
497
|
-
|
498
|
-
Another action is `:find` (which is currently doing the same as `:update`) to find a model by using `params[:id]`.
|
499
|
-
|
500
|
-
|
501
|
-
### Normalizing params
|
502
|
-
|
503
|
-
Override `#setup_params!` to add or remove values to params before the operation is run.
|
504
|
-
|
505
|
-
```ruby
|
506
|
-
class Create < Trailblazer::Operation
|
507
|
-
def process(params)
|
508
|
-
params #=> {show_all: true, admin: true, .. }
|
509
|
-
end
|
510
|
-
|
511
|
-
private
|
512
|
-
def setup_params!(params)
|
513
|
-
params.merge!(show_all: true) if params[:admin]
|
514
|
-
end
|
515
|
-
end
|
516
|
-
end
|
517
|
-
```
|
518
|
-
|
519
|
-
This centralizes params normalization and doesn't require you to do that manually in `#process`.
|
520
|
-
|
521
|
-
### Collections
|
522
|
-
|
523
|
-
Operations can also be used to present (and soon to process) collections of objects, e.g. for an `Index` operation. This is [documented here](http://trailblazerb.org/gems/operation/collection.html).
|
251
|
+
## Representers
|
524
252
|
|
525
|
-
|
253
|
+
Operations can use representers from [Roar](https://github.com/apotonick/roar) to serialize and parse JSON and XML documents for APIs.
|
526
254
|
|
527
|
-
|
255
|
+
Representers can be inferred automatically from your contract, then may be refined, e.g. with hypermedia or a format like `JSON-API`.
|
528
256
|
|
529
257
|
```ruby
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
258
|
+
class Comment::Create < Trailblazer::Operation
|
259
|
+
representer do
|
260
|
+
# inherited :body
|
261
|
+
include Roar::JSON::HAL
|
534
262
|
|
535
|
-
|
536
|
-
# will be run asynchronously.
|
263
|
+
link(:self) { comment_path(represented.id) }
|
537
264
|
end
|
538
|
-
end
|
539
265
|
```
|
540
266
|
|
267
|
+
The operation can then parse incoming JSON documents in `validate` and render a document via `to_json`.
|
541
268
|
|
542
|
-
|
543
|
-
|
269
|
+
[Learn more.](http://trailblazerb.org/gems/operation/representer.html)
|
544
270
|
|
545
|
-
|
546
|
-
|
547
|
-
## Autoloading
|
271
|
+
## Tests
|
548
272
|
|
549
|
-
|
273
|
+
In Trailblazer, you only have operation unit tests and integration smoke tests to test the operation/controller wiring.
|
550
274
|
|
551
|
-
|
275
|
+
Operations completely replace the need for leaky factories.
|
552
276
|
|
553
277
|
```ruby
|
554
|
-
|
278
|
+
describe Comment::Update do
|
279
|
+
let(:comment) { Comment::Create.(comment: {body: "[That](http://trailblazerb.org)!"}) }
|
555
280
|
```
|
556
281
|
|
557
|
-
|
282
|
+
## More
|
558
283
|
|
559
|
-
|
284
|
+
Trailblazer has many more architectural features such as
|
560
285
|
|
561
|
-
|
286
|
+
* Polymorphic builders and operations
|
287
|
+
* Inheritance and composition support
|
288
|
+
* Polymorphic views
|
562
289
|
|
290
|
+
Check the project website and the book.
|
563
291
|
|
564
292
|
## Installation
|
565
293
|
|
@@ -573,40 +301,11 @@ gem "cells"
|
|
573
301
|
|
574
302
|
Cells is _not_ required per default! Add it if you use it, which is highly recommended.
|
575
303
|
|
576
|
-
A few quirks are required at the moment as Rails autoloading is giving us a hard time. The setup of an app [is documented here](https://github.com/apotonick/gemgem-trbrb/wiki/Things-You-Should-Know).
|
577
|
-
|
578
|
-
## Undocumented Features
|
579
|
-
|
580
|
-
(Please don't read this section!)
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
### Named Controller Instance Variables
|
585
|
-
|
586
|
-
If you want to include named instance variables for you views you must include another ActiveRecord specific module.
|
587
|
-
|
588
|
-
```ruby
|
589
|
-
require 'trailblazer/operation/controller/active_record'
|
590
|
-
|
591
|
-
class ApplicationController < ActionController::Base
|
592
|
-
include Trailblazer::Operation::Controller
|
593
|
-
include Trailblazer::Operation::Controller::ActiveRecord
|
594
|
-
end
|
595
|
-
```
|
596
|
-
|
597
|
-
This will setup a named instance variable of your operation's model, for example `@song`.
|
598
|
-
|
599
304
|
## The Book
|
600
305
|
|
601
306
|
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
602
307
|
|
603
|
-
Please buy
|
308
|
+
Please buy it: [Trailblazer - A new architecture for Rails](https://leanpub.com/trailblazer).
|
604
309
|
|
605
310
|
The [demo application](https://github.com/apotonick/gemgem-trbrb) implements what we discuss in the book.
|
606
311
|
|
607
|
-
## Why?
|
608
|
-
|
609
|
-
* Grouping code, views and assets by concepts increases the **maintainability** of your apps. Developers will find their way faster into your structure as the file layout is more intuitive.
|
610
|
-
* Finding bugs gets less frustrating as encapsulated layers allow **testing components** in total isolation. Once you know your form and your view are ok, it must be the parsing code.
|
611
|
-
* The reusability of code increases drastically as Trailblazer gently pushes you towards encapsulation. No more redundant helpers but clean inheritance.
|
612
|
-
* No more surprises from ActiveRecord's massive API. The separation between persistence and domain automatically results in smaller, less destructive APIs for your models.
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -137,7 +137,7 @@ module Trailblazer
|
|
137
137
|
result
|
138
138
|
end
|
139
139
|
|
140
|
-
# When using Op
|
140
|
+
# When using Op.(), an invalid contract will raise an exception.
|
141
141
|
def raise!(contract)
|
142
142
|
raise InvalidContract.new(contract.errors.to_s) if @options[:raise_on_invalid]
|
143
143
|
end
|
@@ -5,13 +5,13 @@ private
|
|
5
5
|
def form(*args)
|
6
6
|
operation!(*args).tap do |op|
|
7
7
|
op.contract.prepopulate! # equals to @form.prepopulate!
|
8
|
-
end
|
8
|
+
end.contract
|
9
9
|
end
|
10
10
|
|
11
11
|
# Provides the operation instance, model and contract without running #process.
|
12
12
|
# Returns the operation.
|
13
|
-
def present(operation_class,
|
14
|
-
operation!(operation_class,
|
13
|
+
def present(operation_class, options={})
|
14
|
+
operation!(operation_class, skip_form: true)
|
15
15
|
end
|
16
16
|
|
17
17
|
def collection(*args)
|
@@ -20,8 +20,8 @@ private
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def run(operation_class,
|
24
|
-
res, op = operation_for!(operation_class,
|
23
|
+
def run(operation_class, options={}, &block)
|
24
|
+
res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
|
25
25
|
|
26
26
|
yield op if res and block_given?
|
27
27
|
|
@@ -29,8 +29,9 @@ private
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# The block passed to #respond is always run, regardless of the validity result.
|
32
|
-
def respond(operation_class, options={},
|
33
|
-
|
32
|
+
def respond(operation_class, options={}, &block)
|
33
|
+
options[:___dont_deprecate] = 1 # TODO: remove in 1.1.
|
34
|
+
res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
|
34
35
|
namespace = options.delete(:namespace) || []
|
35
36
|
|
36
37
|
return respond_with *namespace, op, options if not block_given?
|
@@ -38,8 +39,8 @@ private
|
|
38
39
|
end
|
39
40
|
|
40
41
|
private
|
41
|
-
def operation!(operation_class,
|
42
|
-
res, op = operation_for!(operation_class,
|
42
|
+
def operation!(operation_class, options={}) # or #model or #setup.
|
43
|
+
res, op = operation_for!(operation_class, options) { |params| [true, operation_class.present(params)] }
|
43
44
|
op
|
44
45
|
end
|
45
46
|
|
@@ -47,9 +48,13 @@ private
|
|
47
48
|
end
|
48
49
|
|
49
50
|
# Normalizes parameters and invokes the operation (including its builders).
|
50
|
-
def operation_for!(operation_class,
|
51
|
-
|
52
|
-
|
51
|
+
def operation_for!(operation_class, options, &block)
|
52
|
+
options = deprecate_positional_params_argument!(options)
|
53
|
+
|
54
|
+
# Per default, only treat :html and js as non-document.
|
55
|
+
default_options = {is_document: ![:html, :js].include?(request.format.to_sym)}
|
56
|
+
options = default_options.merge(options)
|
57
|
+
params = options.delete(:params) || self.params # TODO: test params: parameter properly in all 4 methods.
|
53
58
|
|
54
59
|
process_params!(params)
|
55
60
|
res, op = Trailblazer::Endpoint.new(operation_class, params, request, options).(&block)
|
@@ -58,6 +63,19 @@ private
|
|
58
63
|
[res, op]
|
59
64
|
end
|
60
65
|
|
66
|
+
def deprecate_positional_params_argument!(options) # TODO: remove in 1.1.
|
67
|
+
return options if options.has_key?(:skip_form)
|
68
|
+
return options if options.has_key?(:is_document)
|
69
|
+
return options if options.has_key?(:params)
|
70
|
+
return options if options.has_key?(:namespace)
|
71
|
+
return options if options.has_key?(:___dont_deprecate)
|
72
|
+
return options if options.size == 0
|
73
|
+
|
74
|
+
warn "[Trailblazer] The positional params argument for #run, #present, #form and #respond is deprecated and will be removed in 1.1.
|
75
|
+
Please provide a custom params via `run Comment::Create, params: {..}` and have a nice day."
|
76
|
+
{params: options}
|
77
|
+
end
|
78
|
+
|
61
79
|
def setup_operation_instance_variables!(operation, options)
|
62
80
|
@operation = operation
|
63
81
|
@model = operation.model
|
@@ -33,6 +33,7 @@ module Trailblazer::Operation::Representer
|
|
33
33
|
options_from: :deserializer, # use :instance etc. in deserializer.
|
34
34
|
superclass: Representable::Decorator,
|
35
35
|
representer_from: lambda { |inline| inline.representer_class },
|
36
|
+
exclude_options: [:default, :populator] # TODO: test with populator: in an operation.
|
36
37
|
)
|
37
38
|
end
|
38
39
|
end
|
data/lib/trailblazer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|
@@ -237,4 +237,3 @@ test_files:
|
|
237
237
|
- test/representer_test.rb
|
238
238
|
- test/rollback_test.rb
|
239
239
|
- test/test_helper.rb
|
240
|
-
has_rdoc:
|