trailblazer 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -3
  3. data/CHANGES.md +33 -0
  4. data/Gemfile +4 -1
  5. data/README.md +171 -166
  6. data/Rakefile +10 -2
  7. data/gemfiles/Gemfile.rails.lock +42 -11
  8. data/lib/trailblazer/autoloading.rb +4 -3
  9. data/lib/trailblazer/endpoint.rb +40 -0
  10. data/lib/trailblazer/operation.rb +15 -8
  11. data/lib/trailblazer/operation/collection.rb +7 -0
  12. data/lib/trailblazer/operation/controller.rb +30 -22
  13. data/lib/trailblazer/operation/controller/active_record.rb +6 -2
  14. data/lib/trailblazer/operation/crud.rb +3 -5
  15. data/lib/trailblazer/operation/dispatch.rb +29 -0
  16. data/lib/trailblazer/operation/representer.rb +41 -5
  17. data/lib/trailblazer/operation/responder.rb +2 -2
  18. data/lib/trailblazer/operation/uploaded_file.rb +4 -4
  19. data/lib/trailblazer/operation/worker.rb +8 -10
  20. data/lib/trailblazer/rails/railtie.rb +10 -7
  21. data/lib/trailblazer/version.rb +1 -1
  22. data/test/collection_test.rb +56 -0
  23. data/test/crud_test.rb +23 -1
  24. data/test/dispatch_test.rb +63 -0
  25. data/test/operation_test.rb +84 -125
  26. data/test/rails/controller_test.rb +51 -0
  27. data/test/rails/endpoint_test.rb +86 -0
  28. data/test/rails/fake_app/cells.rb +2 -2
  29. data/test/rails/fake_app/config.rb +1 -1
  30. data/test/rails/fake_app/controllers.rb +27 -0
  31. data/test/rails/fake_app/models.rb +10 -1
  32. data/test/rails/fake_app/rails_app.rb +15 -1
  33. data/test/rails/fake_app/song/operations.rb +38 -2
  34. data/test/rails/fake_app/views/bands/index.html.erb +1 -0
  35. data/test/rails/fake_app/views/songs/another_view.html.erb +2 -0
  36. data/test/representer_test.rb +126 -0
  37. data/test/responder_test.rb +2 -4
  38. data/test/rollback_test.rb +47 -0
  39. data/test/test_helper.rb +43 -1
  40. data/test/uploaded_file_test.rb +4 -4
  41. data/test/worker_test.rb +13 -9
  42. data/trailblazer.gemspec +7 -3
  43. metadata +68 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 501c866e1640a0a821e4bbeaa38fbab80ef8c03d
4
- data.tar.gz: b76816095a34ad206dbf1d74fbefd070957f73f4
3
+ metadata.gz: 64d96dc88ee097d82859f27a353220b80c738e3e
4
+ data.tar.gz: 2b94910cfaa58c8b8c1d6ade98d7d1c2d9280c61
5
5
  SHA512:
6
- metadata.gz: 69aed732717c006c7a32f09fabbaa2ff886cb9fd4d6be4fccdf6dd0f81959415c440470705a008570f5e0ccf9bcc9dadfff4cb19e50babb04c0d2dcf77a75cc3
7
- data.tar.gz: 769c47a8a86e3777c7bd6f13a4893b3fe969712be3e1eecfdaadd4f013c082d61abe9fbe2ca4a6cdf47bd69fe2e774a3b7cc4927352fe2601fb1a349007f6eff
6
+ metadata.gz: 7ca6fc8e1715d0c98e07630c900daa3294bc6ac0387b32605efc2dc36d623cd61b43eed532c43255354ed210d564ef8ae58d2639d6bdfb8a826a6a4d9690cf3a
7
+ data.tar.gz: becc502617a59760160be2b64d53e3025be033e4c404ee76d557cdd3c0e780da963ed6e1305cc54d06f14739ef47ce8a9450c4753bc6d97a12db97a4c115e96d
data/.travis.yml CHANGED
@@ -1,8 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.2.0
3
4
  - 2.1.2
4
5
  - 2.0.0
5
6
  - 1.9.3
6
- script:
7
- - bundle exec rake test
8
- - bundle exec rake rails
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
@@ -5,4 +5,7 @@ gemspec
5
5
 
6
6
  # gem "representable", path: "../representable"
7
7
  # gem "reform", path: "../reform"
8
- # gem "reform", git: "https://github.com/apotonick/reform.git"
8
+ # gem "disposable", path: "../disposable"
9
+ gem "virtus"
10
+ gem "reform", github: "apotonick/reform"
11
+
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. It **removes bulky controllers** and strong_parameters by supplying additional layers to hold that code and **completely replaces helpers**.
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 immediately dispatch to an operation. Controllers do not contain any business logic.
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 yield to the block.
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
- Operations encapsulate business logic. One operation per high-level domain _function_ is used. Different formats or environments are handled in subclasses. Operations don't know about HTTP.
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 Comment < ActiveRecord::Base
190
- class Create < Trailblazer::Operation
191
- def process(params)
192
- # do whatever you feel like.
193
- self
194
- end
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
- Using `Operation#[]` will return the operation instance. In case of an invalid operation, this will raise an exception.
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
- Using operations as test factories is a fundamental concept of Trailblazer to remove buggy redundancy in tests and manual factories.
315
+ ### Different Request Formats
218
316
 
219
- ### Run style
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 can run an operation manually and use the same block semantics as found in the controller.
319
+ You need to configure this in the controller.
222
320
 
223
321
  ```ruby
224
- Comment::Create.run(params) do |op|
225
- # only run when valid.
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
- The operation makes use of the form object using the `#validate` method.
327
+ It's up to the operation's builder to decide which class to instantiate.
236
328
 
237
329
  ```ruby
238
- class Comment < ActiveRecord::Base
239
- class Create < Trailblazer::Operation
240
- contract do
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
- This is a familiar work-flow from Reform. Validation does _not_ touch the model.
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
- 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 .
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
- 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.
342
+ ### Call style
288
343
 
344
+ The simplest way of running an operation is the _call style_.
289
345
 
290
- ## Controller API
346
+ ```ruby
347
+ op = Comment::Create[params]
348
+ ```
291
349
 
292
- ### Normalizing params
350
+ Using `Operation#[]` will return the operation instance. In case of an invalid operation, this will raise an exception.
293
351
 
294
- Override `#process_params!` to add or remove values to `params` before the operation is run. This is called in `#run`, `#respond` and `#present`.
352
+ Note how this can easily be used for test factories.
295
353
 
296
354
  ```ruby
297
- class CommentsController < ApplicationController
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
- This centralizes params normalization and doesn't require you to do that in every action manually.
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
- ### Different Request Formats
360
+ ### Run style
311
361
 
312
- The controller helpers `#present` and `#respond` automatically pass the request body into the operation via the `params` hash. It's up to the operation's builder to decide which class to instantiate.
362
+ You can run an operation manually and use the same block semantics as found in the controller.
313
363
 
314
364
  ```ruby
315
- class Create < Trailblazer::Operation
316
- builds do |params|
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
- [Note that this will soon be provided with a module.]
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 #setup_params! to add or remove values to params before the operation is run.
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 process_params!(params)
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.