trailblazer 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGES.md +33 -0
- data/Gemfile +4 -1
- data/README.md +171 -166
- data/Rakefile +10 -2
- data/gemfiles/Gemfile.rails.lock +42 -11
- data/lib/trailblazer/autoloading.rb +4 -3
- data/lib/trailblazer/endpoint.rb +40 -0
- data/lib/trailblazer/operation.rb +15 -8
- data/lib/trailblazer/operation/collection.rb +7 -0
- data/lib/trailblazer/operation/controller.rb +30 -22
- data/lib/trailblazer/operation/controller/active_record.rb +6 -2
- data/lib/trailblazer/operation/crud.rb +3 -5
- data/lib/trailblazer/operation/dispatch.rb +29 -0
- data/lib/trailblazer/operation/representer.rb +41 -5
- data/lib/trailblazer/operation/responder.rb +2 -2
- data/lib/trailblazer/operation/uploaded_file.rb +4 -4
- data/lib/trailblazer/operation/worker.rb +8 -10
- data/lib/trailblazer/rails/railtie.rb +10 -7
- data/lib/trailblazer/version.rb +1 -1
- data/test/collection_test.rb +56 -0
- data/test/crud_test.rb +23 -1
- data/test/dispatch_test.rb +63 -0
- data/test/operation_test.rb +84 -125
- data/test/rails/controller_test.rb +51 -0
- data/test/rails/endpoint_test.rb +86 -0
- data/test/rails/fake_app/cells.rb +2 -2
- data/test/rails/fake_app/config.rb +1 -1
- data/test/rails/fake_app/controllers.rb +27 -0
- data/test/rails/fake_app/models.rb +10 -1
- data/test/rails/fake_app/rails_app.rb +15 -1
- data/test/rails/fake_app/song/operations.rb +38 -2
- data/test/rails/fake_app/views/bands/index.html.erb +1 -0
- data/test/rails/fake_app/views/songs/another_view.html.erb +2 -0
- data/test/representer_test.rb +126 -0
- data/test/responder_test.rb +2 -4
- data/test/rollback_test.rb +47 -0
- data/test/test_helper.rb +43 -1
- data/test/uploaded_file_test.rb +4 -4
- data/test/worker_test.rb +13 -9
- data/trailblazer.gemspec +7 -3
- metadata +68 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64d96dc88ee097d82859f27a353220b80c738e3e
|
4
|
+
data.tar.gz: 2b94910cfaa58c8b8c1d6ade98d7d1c2d9280c61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ca6fc8e1715d0c98e07630c900daa3294bc6ac0387b32605efc2dc36d623cd61b43eed532c43255354ed210d564ef8ae58d2639d6bdfb8a826a6a4d9690cf3a
|
7
|
+
data.tar.gz: becc502617a59760160be2b64d53e3025be033e4c404ee76d557cdd3c0e780da963ed6e1305cc54d06f14739ef47ce8a9450c4753bc6d97a12db97a4c115e96d
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# 0.3.0
|
2
|
+
|
3
|
+
## Changes
|
4
|
+
|
5
|
+
* In Railtie, use `ActionDispatch::Reloader.to_prepare` for autoloading, nothing else. This should fix spring reloading.
|
6
|
+
* Allow `Op#validate(params, model, Contract)` with CRUD.
|
7
|
+
* Allows prefixed table names, e.g. `admin.users` in `Controller`. The instance variables will be `@user`. Thanks to @fernandes and especially @HuckyDucky.
|
8
|
+
* Added `Operation::Collection` which will allow additional behavior like pagination and scoping. Thanks to @fernandes for his work on this.
|
9
|
+
* Added `Operation::collection` to run `setup!` without instantiating a contract. This is called in the new `Controller#collection` method.
|
10
|
+
* Added `Operation#model` as this is a fundamental concept now.
|
11
|
+
* Improved the undocumented `Representer` module which allows inferring representers from contract, using them to deserialize documents for the form, and rendering documents.
|
12
|
+
* Changed `Operation::Dispatch` which now provides imperative callbacks.
|
13
|
+
|
14
|
+
## API change
|
15
|
+
|
16
|
+
1. The return value of #process is no longer returned from ::run and ::call. They always return the operation instance.
|
17
|
+
2. The return value of #validate is `true` or `false`. This allows a more intuitive operation body.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
def process(params)
|
21
|
+
if validate(params)
|
22
|
+
.. do valid
|
23
|
+
else
|
24
|
+
.. handle invalid
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
* `Worker` only works with Reform >= 2.0.0.
|
30
|
+
|
31
|
+
# 0.2.3
|
32
|
+
|
33
|
+
|
1
34
|
# 0.2.2
|
2
35
|
|
3
36
|
# Added Operation#errors as every operation maintains a contract.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,13 +2,7 @@
|
|
2
2
|
|
3
3
|
_Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture._
|
4
4
|
|
5
|
-
In a nutshell: Trailblazer makes you write **logicless models** that purely act as data objects, don't contain callbacks, nested attributes, validations or domain logic.
|
6
|
-
|
7
|
-
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
8
|
-
|
9
|
-
Please buy my book [Trailblazer - A new architecture for Rails](https://leanpub.com/trailblazer) and [let me know](http://twitter.com/apotonick) what you think! I am still working on the book but keep adding new chapters every other week. It will be about 300 pages and we're developing a real, full-blown Rails/Trb application.
|
10
|
-
|
11
|
-
The [demo application](https://github.com/apotonick/gemgem-trbrb) implements what we discuss in the book.
|
5
|
+
In a nutshell: Trailblazer makes you write **logicless models** that purely act as data objects, don't contain callbacks, nested attributes, validations or domain logic. **Controllers** become lean HTTP endpoints. Your **business logic** (including validation) is decoupled from the actual Rails framework and modeled in _operations_.
|
12
6
|
|
13
7
|
## Mission
|
14
8
|
|
@@ -55,14 +49,127 @@ Trailblazer uses Rails routing to map URLs to controllers (we will add simplific
|
|
55
49
|
|
56
50
|
## Controllers
|
57
51
|
|
58
|
-
Controllers are lean endpoints for HTTP. They differentiate between request formats like HTML or JSON and
|
52
|
+
Controllers are lean endpoints for HTTP. They differentiate between request formats like HTML or JSON and do not contain any business logic. Actions immediately dispatch to an operation.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class CommentsController < ApplicationController
|
56
|
+
def create
|
57
|
+
Comment::Create.(params)
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
This can be simplified using the `run` method and allows you a simple conditional to handle failing operations.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class CommentsController < ApplicationController
|
65
|
+
def create
|
66
|
+
run Comment::Create do |op|
|
67
|
+
return redirect_to(comment_path op.model) # success.
|
68
|
+
end
|
69
|
+
|
70
|
+
render :new # re-render form.
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Again, the controller only knows how to dispatch to the operation and what to do for success and invalid processing. While business affairs (e.g. logging or rollbacks) are to be handled in the operation, the controller invokes rendering or redirecting.
|
75
|
+
|
76
|
+
## Operation
|
77
|
+
|
78
|
+
The [API is documented here](http://trailblazerb.org/gems/operation/api.html).
|
79
|
+
|
80
|
+
Operations encapsulate business logic and are the heart of a Trailblazer architecture. One operation per high-level domain _function_ is used. Different formats or environments are handled in subclasses. Operations don't know about HTTP or the environment.
|
81
|
+
|
82
|
+
An operation is not just a monolithic replacement for your business code. An operation is a simple orchestrator between the form object, models and your business code.
|
83
|
+
|
84
|
+
You don't have to use the form/contract if you don't want it, BTW.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class Comment < ActiveRecord::Base
|
88
|
+
class Create < Trailblazer::Operation
|
89
|
+
def process(params)
|
90
|
+
# do whatever you feel like.
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Operations only need to implement `#process` which receives the params from the caller.
|
97
|
+
|
98
|
+
## Validations
|
99
|
+
|
100
|
+
Operations usually have 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.
|
101
|
+
|
102
|
+
The operation makes use of the form object using the `#validate` method.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Comment < ActiveRecord::Base
|
106
|
+
class Create < Trailblazer::Operation
|
107
|
+
contract do
|
108
|
+
property :body, validates: {presence: true}
|
109
|
+
end
|
110
|
+
|
111
|
+
def process(params)
|
112
|
+
@model = Comment.new
|
113
|
+
|
114
|
+
validate(params[:comment], @model) do |f|
|
115
|
+
f.save
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
The contract (aka _form_) is defined in the `::contract` block. You can implement nested forms, default values, validations, and everything else Reform provides.
|
123
|
+
|
124
|
+
In case of a valid form the block for `#validate` is invoked. It receives the populated form object. You can use the form to save data or write your own logic. This is where your business logic is implemented, and in turn could be an invocation of service objects or organizers.
|
125
|
+
|
126
|
+
Technically speaking, what really happens in `Operation#validate` is the following.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
contract_class.new(@model).validate(params[:comment])
|
130
|
+
```
|
131
|
+
|
132
|
+
This is a familiar work-flow from Reform. Validation does _not_ touch the model.
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
If you prefer keeping your forms in separate classes or even files, you're free to do so.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class Create < Trailblazer::Operation
|
141
|
+
self.contract_class = MyCommentForm
|
142
|
+
```
|
143
|
+
|
144
|
+
## Models
|
145
|
+
|
146
|
+
Models for persistence can be implemented using any ORM you fancy, for instance [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord#active-record--object-relational-mapping-in-rails) or [Datamapper](http://datamapper.org/).
|
147
|
+
|
148
|
+
In Trailblazer, models are completely empty and solely configure database-relevant directives and associations. No business logic is allowed in models. Only operations, views and cells can access models directly.
|
149
|
+
|
150
|
+
## Views
|
151
|
+
|
152
|
+
View rendering can happen using the controller as known from Rails. This is absolutely fine for simple views.
|
153
|
+
|
154
|
+
More complex UI logic happens in _View Models_ as found in [Cells](https://github.com/apotonick/cells). View models also replace helpers.
|
155
|
+
|
156
|
+
8. **HTTP API** Consuming and rendering API documents (e.g. JSON or XML) is done via [roar](https://github.com/apotonick/roar) and [representable](https://github.com/apotonick/representable). They usually inherit the schema from <em>Contract</em>s .
|
157
|
+
|
158
|
+
10. **Tests** Subject to tests are mainly <em>Operation</em>s and <em>View Model</em>s, as they encapsulate endpoint behaviour of your app. As a nice side effect, factories are replaced by simple _Operation_ calls.
|
159
|
+
|
160
|
+
## Overview
|
161
|
+
|
162
|
+
Trailblazer is basically a mash-up of mature gems that have been developed over the past 10 years and are used in hundreds and thousands of production apps.
|
163
|
+
|
164
|
+
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.
|
165
|
+
|
166
|
+
## Controller API
|
59
167
|
|
60
168
|
Trailblazer provides four methods to present and invoke operations. But before that, you need to include the `Controller` module.
|
61
169
|
|
62
170
|
```ruby
|
63
171
|
class CommentsController < ApplicationController
|
64
172
|
include Trailblazer::Operation::Controller
|
65
|
-
|
66
173
|
```
|
67
174
|
|
68
175
|
### Rendering the form object
|
@@ -115,7 +222,7 @@ def create
|
|
115
222
|
end
|
116
223
|
```
|
117
224
|
|
118
|
-
Note that the operation instance is
|
225
|
+
Note that the operation instance is yielded to the block.
|
119
226
|
|
120
227
|
The case of an invalid response can be handled after the block.
|
121
228
|
|
@@ -144,6 +251,12 @@ end
|
|
144
251
|
|
145
252
|
This will simply run the operation and chuck the instance into the responder letting the latter sort out what to render or where to redirect. The operation delegates respective calls to its internal `model`.
|
146
253
|
|
254
|
+
`#respond` will accept options to be passed on to `respond_with`, too
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
respond Comment::Create, params, location: brandnew_comments_path
|
258
|
+
```
|
259
|
+
|
147
260
|
You can also handle different formats in that block. It is totally fine to do that in the controller as this is _endpoint_ logic that is HTTP-specific and not business.
|
148
261
|
|
149
262
|
```ruby
|
@@ -175,154 +288,86 @@ For document-based APIs and request types that are not HTTP the operation will b
|
|
175
288
|
|
176
289
|
Note that `#present` will leave rendering up to you - `respond_to` is _not_ called.
|
177
290
|
|
178
|
-
### Controller API
|
179
291
|
|
180
292
|
In all three cases the following instance variables are assigned: `@operation`, `@form`, `@model`.
|
181
293
|
|
182
294
|
Named instance variables can be included, too. This is documented [here](#named-controller-instance-variables).
|
183
295
|
|
184
|
-
## Operation
|
185
296
|
|
186
|
-
|
297
|
+
### Normalizing params
|
298
|
+
|
299
|
+
Override `#process_params!` to add or remove values to `params` before the operation is run. This is called in `#run`, `#respond` and `#present`.
|
187
300
|
|
188
301
|
```ruby
|
189
|
-
class
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
302
|
+
class CommentsController < ApplicationController
|
303
|
+
# ..
|
304
|
+
|
305
|
+
private
|
306
|
+
def process_params!(params)
|
307
|
+
params.merge!(current_user: current_user)
|
195
308
|
end
|
196
309
|
end
|
197
|
-
```
|
198
|
-
|
199
|
-
Operations only need to implement `#process` which receives the params from the caller.
|
200
|
-
|
201
|
-
### Call style
|
202
|
-
|
203
|
-
The simplest way of running an operation is the _call style_.
|
204
|
-
|
205
|
-
```ruby
|
206
|
-
op = Comment::Create[params]
|
207
310
|
```
|
208
311
|
|
209
|
-
|
210
|
-
|
211
|
-
Note how this can easily be used for test factories.
|
312
|
+
This centralizes params normalization and doesn't require you to do that in every action manually.
|
212
313
|
|
213
|
-
```ruby
|
214
|
-
let(:comment) { Comment::Create[valid_comment_params].model }
|
215
|
-
```
|
216
314
|
|
217
|
-
|
315
|
+
### Different Request Formats
|
218
316
|
|
219
|
-
|
317
|
+
In case you have document-API operations that use representers to deserialize the incoming JSON or XML: You can configure the controller to pass the original request body into the operation via `params["comment"]` - instead of the pre-parsed hash from Rails.
|
220
318
|
|
221
|
-
You
|
319
|
+
You need to configure this in the controller.
|
222
320
|
|
223
321
|
```ruby
|
224
|
-
|
225
|
-
|
226
|
-
end
|
322
|
+
class CommentsController < ApplicationController
|
323
|
+
operation document_formats: :json
|
227
324
|
```
|
228
325
|
|
229
|
-
Of course, this does _not_ throw an exception but simply skips the block when the operation is invalid.
|
230
|
-
|
231
|
-
## Validations
|
232
|
-
|
233
|
-
Operations usually have 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.
|
234
326
|
|
235
|
-
|
327
|
+
It's up to the operation's builder to decide which class to instantiate.
|
236
328
|
|
237
329
|
```ruby
|
238
|
-
class
|
239
|
-
|
240
|
-
|
241
|
-
property :body, validates: {presence: true}
|
242
|
-
end
|
243
|
-
|
244
|
-
def process(params)
|
245
|
-
@model = Comment.new
|
246
|
-
|
247
|
-
validate(params[:comment], @model) do |f|
|
248
|
-
f.save
|
249
|
-
end
|
250
|
-
end
|
330
|
+
class Create < Trailblazer::Operation
|
331
|
+
builds do |params|
|
332
|
+
JSON if params[:format] == "json"
|
251
333
|
end
|
252
334
|
end
|
253
|
-
```
|
254
|
-
|
255
|
-
The contract (aka _form_) is defined in the `::contract` block. You can implement nested forms, default values, validations, and everything else Reform provides.
|
256
|
-
|
257
|
-
In case of a valid form the block for `#validate` is invoked. It receives the populated form object. You can use the form to save data or write your own logic.
|
258
|
-
|
259
|
-
Technically speaking, what really happens in `Operation#validate` is the following.
|
260
|
-
|
261
|
-
```ruby
|
262
|
-
contract_class.new(@model).validate(params[:comment])
|
263
335
|
```
|
264
336
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
## Models
|
269
|
-
|
270
|
-
Models for persistence can be implemented using any ORM you fancy, for instance [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord#active-record--object-relational-mapping-in-rails) or [Datamapper](http://datamapper.org/).
|
271
|
-
|
272
|
-
In Trailblazer, models are completely empty and solely configure database-relevant directives and associations. No business logic is allowed in models. Only operations, views and cells can access models directly.
|
273
|
-
|
274
|
-
## Views
|
275
|
-
|
276
|
-
View rendering can happen using the controller as known from Rails. This is absolutely fine for simple views.
|
277
|
-
|
278
|
-
More complex UI logic happens in _View Models_ as found in [Cells](https://github.com/apotonick/cells). View models also replace helpers.
|
279
|
-
|
280
|
-
|
337
|
+
[Note that this will soon be provided with a module.]
|
281
338
|
|
282
339
|
|
283
|
-
|
284
|
-
|
285
|
-
10. **Tests** Subject to tests are mainly <em>Operation</em>s and <em>View Model</em>s, as they encapsulate endpoint behaviour of your app. As a nice side effect, factories are replaced by simple _Operation_ calls.
|
340
|
+
## Operation API
|
286
341
|
|
287
|
-
|
342
|
+
### Call style
|
288
343
|
|
344
|
+
The simplest way of running an operation is the _call style_.
|
289
345
|
|
290
|
-
|
346
|
+
```ruby
|
347
|
+
op = Comment::Create[params]
|
348
|
+
```
|
291
349
|
|
292
|
-
|
350
|
+
Using `Operation#[]` will return the operation instance. In case of an invalid operation, this will raise an exception.
|
293
351
|
|
294
|
-
|
352
|
+
Note how this can easily be used for test factories.
|
295
353
|
|
296
354
|
```ruby
|
297
|
-
|
298
|
-
# ..
|
299
|
-
|
300
|
-
private
|
301
|
-
def process_params!(params)
|
302
|
-
params.merge!(current_user: current_user)
|
303
|
-
end
|
304
|
-
end
|
355
|
+
let(:comment) { Comment::Create[valid_comment_params].model }
|
305
356
|
```
|
306
357
|
|
307
|
-
|
308
|
-
|
358
|
+
Using operations as test factories is a fundamental concept of Trailblazer to remove buggy redundancy in tests and manual factories.
|
309
359
|
|
310
|
-
###
|
360
|
+
### Run style
|
311
361
|
|
312
|
-
|
362
|
+
You can run an operation manually and use the same block semantics as found in the controller.
|
313
363
|
|
314
364
|
```ruby
|
315
|
-
|
316
|
-
|
317
|
-
JSON if params[:format] == "json"
|
318
|
-
end
|
365
|
+
Comment::Create.run(params) do |op|
|
366
|
+
# only run when valid.
|
319
367
|
end
|
320
368
|
```
|
321
369
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
## Operation API
|
370
|
+
Of course, this does _not_ throw an exception but simply skips the block when the operation is invalid.
|
326
371
|
|
327
372
|
### CRUD Semantics
|
328
373
|
|
@@ -366,7 +411,7 @@ Another action is `:find` (which is currently doing the same as `:update`) to fi
|
|
366
411
|
|
367
412
|
### Normalizing params
|
368
413
|
|
369
|
-
Override
|
414
|
+
Override `#setup_params!` to add or remove values to params before the operation is run.
|
370
415
|
|
371
416
|
```ruby
|
372
417
|
class Create < Trailblazer::Operation
|
@@ -375,7 +420,7 @@ class Create < Trailblazer::Operation
|
|
375
420
|
end
|
376
421
|
|
377
422
|
private
|
378
|
-
def
|
423
|
+
def setup_params!(params)
|
379
424
|
params.merge!(show_all: true) if params[:admin]
|
380
425
|
end
|
381
426
|
end
|
@@ -384,6 +429,9 @@ end
|
|
384
429
|
|
385
430
|
This centralizes params normalization and doesn't require you to do that manually in `#process`.
|
386
431
|
|
432
|
+
### Collections
|
433
|
+
|
434
|
+
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).
|
387
435
|
|
388
436
|
### Background Processing
|
389
437
|
|
@@ -401,31 +449,6 @@ class Comment::Image::Crop < Trailblazer::Operation
|
|
401
449
|
end
|
402
450
|
```
|
403
451
|
|
404
|
-
### Rendering Operation's Form
|
405
|
-
|
406
|
-
You have access to an operation's form using `::present`.
|
407
|
-
|
408
|
-
```ruby
|
409
|
-
Comment::Create.present(params)
|
410
|
-
```
|
411
|
-
|
412
|
-
This will run the operation's `#process` method _without_ the validate block and return the contract.
|
413
|
-
|
414
|
-
### Marking Operation as Invalid
|
415
|
-
|
416
|
-
Sometimes you don't need a form object but still want the validity behavior of an operation.
|
417
|
-
|
418
|
-
```ruby
|
419
|
-
def process(params)
|
420
|
-
return invalid! unless params[:id]
|
421
|
-
|
422
|
-
Comment.find(params[:id]).destroy
|
423
|
-
self
|
424
|
-
end
|
425
|
-
```
|
426
|
-
|
427
|
-
`#invalid!` returns `self` per default but accepts any result.
|
428
|
-
|
429
452
|
|
430
453
|
### Worker::FileMarshaller: needs representable 2.1.1 (.schema)
|
431
454
|
|
@@ -461,33 +484,7 @@ This will go through `app/concepts/`, find all the `crud.rb` files, autoload the
|
|
461
484
|
|
462
485
|
(Please don't read this section!)
|
463
486
|
|
464
|
-
### Additional Model Setup
|
465
|
-
|
466
|
-
Override `Operation#setup_model(params)` to add nested objects that can be infered from `params` or are static.
|
467
|
-
|
468
|
-
This is called right after `#model!`.
|
469
|
-
|
470
|
-
### Validation Errors
|
471
|
-
|
472
|
-
You can access the contracts `Errors` object via `Operation#errors`.
|
473
487
|
|
474
|
-
### ActiveModel Semantics
|
475
|
-
|
476
|
-
When using `Reform::Form::ActiveModel` (which is used automatically in a Rails environment to make form builders work) you need to invoke `model Comment` in the contract. This can be inferred automatically from the operation by including `CRUD::ActiveModel`.
|
477
|
-
|
478
|
-
```ruby
|
479
|
-
class Create < Trailblazer::Operation
|
480
|
-
include CRUD
|
481
|
-
include CRUD::ActiveModel
|
482
|
-
|
483
|
-
model Comment
|
484
|
-
|
485
|
-
contract do # no need to call ::model, here.
|
486
|
-
property :text
|
487
|
-
end
|
488
|
-
```
|
489
|
-
|
490
|
-
If you want that in all CRUD operations, check out [how you can include](https://github.com/apotonick/gemgem-trbrb/blob/chapter-5/config/initializers/trailblazer.rb#L26) it automatically.
|
491
488
|
|
492
489
|
### Named Controller Instance Variables
|
493
490
|
|
@@ -504,6 +501,14 @@ end
|
|
504
501
|
|
505
502
|
This will setup a named instance variable of your operation's model, for example `@song`.
|
506
503
|
|
504
|
+
## The Book
|
505
|
+
|
506
|
+
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
507
|
+
|
508
|
+
Please buy my book [Trailblazer - A new architecture for Rails](https://leanpub.com/trailblazer) and [let me know](http://twitter.com/apotonick) what you think! I am still working on the book but keep adding new chapters every other week. It will be about 300 pages and we're developing a real, full-blown Rails/Trb application.
|
509
|
+
|
510
|
+
The [demo application](https://github.com/apotonick/gemgem-trbrb) implements what we discuss in the book.
|
511
|
+
|
507
512
|
## Why?
|
508
513
|
|
509
514
|
* 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.
|