trailblazer 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 994926094dd0136c1436127773dd4c8786cd3cb3
4
- data.tar.gz: ce869dcf9f8c01e2fe10847e3d4b54e460537960
3
+ metadata.gz: 973befd1d4260ea9bee84355c8b204084a51b5f9
4
+ data.tar.gz: 41e5430171b1d65a99716fd0502a9b9c8eb9d078
5
5
  SHA512:
6
- metadata.gz: 26367bea09297a1bbcf29506d589826f3e83fca5dc45e4bb6d7146a57f2984c60d324cda43f55a52cd08f3523ed5e76d5b0c56bbddd531a2509ccd4e81117861
7
- data.tar.gz: ff56573c794feb3afbbc7bac40026f65f418153e9aaef9cbdb6967a01d37412ba1faa95fb4127bab82efd6ad0f5ef10a2ea359a8f94bd8749dbebd480b64d8a2
6
+ metadata.gz: a54b22de4bd4966d6045fc78b0a0720fe1d7d4fbfc044115e476eb669fc90cbac645fcb9f780806ddc89c1ac6de7b984341956a703d35954f3e2c5730e64e1cb
7
+ data.tar.gz: 495cccf41dbb172fce76dcf9b5b8de68e66961ae78efcc1bfc5491400fa40fb6cb84e9343d56e827fe991f203ea00c6df547299ca13c87c4242126109215bb3a
@@ -2,4 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1.2
4
4
  - 2.0.0
5
- - 1.9.3
5
+ - 1.9.3
6
+ script:
7
+ - bundle exec rake test
8
+ - bundle exec rake rails
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.2.1
2
+
3
+ * Added `Operation#setup_model!(params)` that can be overridden to add nested objects or process models right after `model!`. Don't add deserialization logic here, let Reform/Representable do that.
4
+ * Added `Operation#setup_params!(params)` to normalize parameters before `#process`. Thanks to @gogogarrett.
5
+ * Added `Controller::ActiveRecord` that will setup a named controller instance variable for your operation model. Thanks @gogogarrett!
6
+ * Added `CRUD::ActiveModel` that currently infers the contract's `::model` from the operation's model.
7
+
1
8
  # 0.2.0
2
9
 
3
10
  ## API Changes
data/README.md CHANGED
@@ -179,6 +179,8 @@ Note that `#present` will leave rendering up to you - `respond_to` is _not_ call
179
179
 
180
180
  In all three cases the following instance variables are assigned: `@operation`, `@form`, `@model`.
181
181
 
182
+ Named instance variables can be included, too. This is documented [here](#named-controller-instance-variables).
183
+
182
184
  ## Operation
183
185
 
184
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.
@@ -361,6 +363,28 @@ end
361
363
 
362
364
  Another action is `:find` (which is currently doing the same as `:update`) to find a model by using `params[:id]`.
363
365
 
366
+
367
+ ### Normalizing params
368
+
369
+ Override #setup_params! to add or remove values to params before the operation is run.
370
+
371
+ ```ruby
372
+ class Create < Trailblazer::Operation
373
+ def process(params)
374
+ params #=> {show_all: true, admin: true, .. }
375
+ end
376
+
377
+ private
378
+ def process_params!(params)
379
+ params.merge!(show_all: true) if params[:admin]
380
+ end
381
+ end
382
+ end
383
+ ```
384
+
385
+ This centralizes params normalization and doesn't require you to do that manually in `#process`.
386
+
387
+
364
388
  ### Background Processing
365
389
 
366
390
  To run an operation in Sidekiq (ActiveJob-support coming!) all you need to do is include the `Worker` module.
@@ -433,6 +457,49 @@ require "trailblazer/rails/railtie"
433
457
  This will go through `app/concepts/`, find all the `crud.rb` files, autoload their corresponding namespace (e.g. `Thing`, which is a model) and then load the `crud.rb` file.
434
458
 
435
459
 
460
+ ## Undocumented Features
461
+
462
+ (Please don't read this section!)
463
+
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
+ ### ActiveModel Semantics
471
+
472
+ 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`.
473
+
474
+ ```ruby
475
+ class Create < Trailblazer::Operation
476
+ include CRUD
477
+ include CRUD::ActiveModel
478
+
479
+ model Comment
480
+
481
+ contract do # no need to call ::model, here.
482
+ property :text
483
+ end
484
+ ```
485
+
486
+ 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.
487
+
488
+ ### Named Controller Instance Variables
489
+
490
+ If you want to include named instance variables for you views you must include another ActiveRecord specific module.
491
+
492
+ ```ruby
493
+ require 'trailblazer/operation/controller/active_record'
494
+
495
+ class ApplicationController < ActionController::Base
496
+ include Trailblazer::Operation::Controller
497
+ include Trailblazer::Operation::Controller::ActiveRecord
498
+ end
499
+ ```
500
+
501
+ This will setup a named instance variable of your operation's model, for example `@song`.
502
+
436
503
  ## Why?
437
504
 
438
505
  * 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.
@@ -46,7 +46,6 @@ module Trailblazer
46
46
  contract_class.class_eval(&block)
47
47
  end
48
48
 
49
-
50
49
  private
51
50
  def build_operation_class(*params)
52
51
  class_builder.call(*params) # Uber::Builder::class_builder
@@ -55,7 +54,6 @@ module Trailblazer
55
54
 
56
55
  include Uber::Builder
57
56
 
58
-
59
57
  def initialize(options={})
60
58
  @valid = true
61
59
  # DISCUSS: use reverse_merge here?
@@ -83,15 +81,24 @@ module Trailblazer
83
81
  end
84
82
 
85
83
  private
86
-
87
84
  def setup!(*params)
85
+ setup_params!(*params)
86
+
88
87
  @model = model!(*params)
88
+ setup_model!(*params)
89
89
  end
90
90
 
91
91
  # Implement #model! to find/create your operation model (if required).
92
92
  def model!(*params)
93
93
  end
94
94
 
95
+ # Override to add attributes that can be infered from params.
96
+ def setup_model!(*params)
97
+ end
98
+
99
+ def setup_params!(*params)
100
+ end
101
+
95
102
  def validate(params, model, contract_class=nil) # NOT to be overridden?!! it creates Result for us.
96
103
  @contract = contract_for(contract_class, model)
97
104
 
@@ -71,9 +71,13 @@ private
71
71
  end
72
72
 
73
73
  res, @operation = yield # Create.run(params)
74
- @form = @operation.contract
75
- @model = @operation.model
74
+ setup_operation_instance_variables!
76
75
 
77
76
  [res, @operation] # DISCUSS: do we need result here? or can we just go pick op.valid?
78
77
  end
78
+
79
+ def setup_operation_instance_variables!
80
+ @form = @operation.contract
81
+ @model = @operation.model
82
+ end
79
83
  end
@@ -0,0 +1,12 @@
1
+ # Assigns an additional instance variable for +@model+ named after the model's table name (e.g. @comment).
2
+ module Trailblazer::Operation::Controller::ActiveRecord
3
+ private
4
+ def setup_operation_instance_variables!
5
+ super
6
+ instance_variable_set(:"@#{operation_model_name}", @model)
7
+ end
8
+
9
+ def operation_model_name
10
+ @model.class.table_name.singularize
11
+ end
12
+ end
@@ -5,13 +5,18 @@ module Trailblazer
5
5
  module CRUD
6
6
  attr_reader :model
7
7
 
8
- def self.included(base)
9
- base.extend Uber::InheritableAttr
10
- base.inheritable_attr :config
11
- base.config = {}
8
+ module Included
9
+ def included(base)
10
+ base.extend Uber::InheritableAttr
11
+ base.inheritable_attr :config
12
+ base.config = {}
12
13
 
13
- base.extend ClassMethods
14
+ base.extend ClassMethods
15
+ end
14
16
  end
17
+ # this makes ::included overrideable, e.g. to add more featues like CRUD::ActiveModel.
18
+ extend Included
19
+
15
20
 
16
21
  module ClassMethods
17
22
  def model(name, action=nil)
@@ -56,6 +61,24 @@ module Trailblazer
56
61
  end
57
62
 
58
63
  alias_method :find_model, :update_model
64
+
65
+
66
+ # Rails-specific.
67
+ # ActiveModel will automatically call Form::model when creating the contract and passes
68
+ # the operation's +::model+, so you don't have to call it twice.
69
+ # This assumes that the form class includes Form::ActiveModel, though.
70
+ module ActiveModel
71
+ def self.included(base)
72
+ base.extend ClassMethods
73
+ end
74
+
75
+ module ClassMethods
76
+ def contract(&block)
77
+ super
78
+ contract_class.model(model_class) # this assumes that Form::ActiveModel is mixed in.
79
+ end
80
+ end
81
+ end
59
82
  end
60
83
  end
61
84
  end
@@ -11,7 +11,9 @@ module Trailblazer
11
11
  end
12
12
 
13
13
  # thank you, http://stackoverflow.com/a/17573888/465070
14
- initializer 'trailblazer.install' do |app|
14
+ initializer 'trailblazer.install', after: :load_config_initializers do |app|
15
+ # the trb autoloading has to be run after initializers have been loaded, so we can tweak inclusion of features in
16
+ # initializers.
15
17
  if Rails.configuration.cache_classes
16
18
  Trailblazer::Railtie.autoload_crud_operations(app)
17
19
  else
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -101,6 +101,18 @@ class CrudTest < MiniTest::Spec
101
101
  ModelUpdateOperation[{id: 1, song: {title: "Mercy Day For Mr. Vengeance"}}].model.must_equal song
102
102
  end
103
103
 
104
+
105
+ # Op#setup_model!
106
+ class SetupModelOperation < CreateOperation
107
+ def setup_model!(params)
108
+ model.instance_eval { @params = params; def params; @params.to_s; end }
109
+ end
110
+ end
111
+
112
+ it { SetupModelOperation[song: {title: "Emily Kane"}].model.params.must_equal "{:song=>{:title=>\"Emily Kane\"}}" }
113
+
114
+
115
+
104
116
  # no call to ::model raises error.
105
117
  class NoModelOperation < Trailblazer::Operation
106
118
  include CRUD
@@ -112,4 +124,21 @@ class CrudTest < MiniTest::Spec
112
124
 
113
125
  # uses :create as default if not set via ::action.
114
126
  it { assert_raises(RuntimeError){ NoModelOperation[{}] } }
127
+
128
+
129
+
130
+ # contract infers model_name.
131
+ # TODO: this a Rails/ActiveModel-specific test.
132
+ class ContractKnowsModelNameOperation < Trailblazer::Operation
133
+ include CRUD
134
+ model Song
135
+ include CRUD::ActiveModel
136
+
137
+ contract do
138
+ include Reform::Form::ActiveModel # this usually happens in Reform::Form::Rails.
139
+ property :title
140
+ end
141
+ end
142
+
143
+ it { ContractKnowsModelNameOperation.present(song: {title: "Direct Hit"}).contract.class.model_name.to_s.must_equal "CrudTest::Song" }
115
144
  end
@@ -7,6 +7,20 @@ module Comparable
7
7
  end
8
8
  end
9
9
 
10
+ class OperationSetupParamsTest < MiniTest::Spec
11
+ class OperationSetupParam < Trailblazer::Operation
12
+ def process(params)
13
+ params
14
+ end
15
+
16
+ def setup_params!(params)
17
+ params.merge!(garrett: "Rocks!")
18
+ end
19
+ end
20
+
21
+ let (:operation) { OperationSetupParam.new }
22
+ it { OperationSetupParam.run({valid: true}).must_equal [true, {valid: true, garrett: "Rocks!"}] }
23
+ end
10
24
 
11
25
  class OperationRunTest < MiniTest::Spec
12
26
  class Operation < Trailblazer::Operation
@@ -93,7 +107,6 @@ class OperationRunTest < MiniTest::Spec
93
107
  it { Operation.(true).contract.must_equal contract }
94
108
  end
95
109
 
96
-
97
110
  class OperationTest < MiniTest::Spec
98
111
  class Operation < Trailblazer::Operation
99
112
  def process(params)
@@ -172,7 +185,6 @@ class OperationTest < MiniTest::Spec
172
185
  # ::[]
173
186
  it { OperationWithManualValid.(Object).must_equal(Object) }
174
187
 
175
-
176
188
  # re-assign params
177
189
  class OperationReassigningParams < Trailblazer::Operation
178
190
  def process(params)
@@ -184,7 +196,6 @@ class OperationTest < MiniTest::Spec
184
196
  # ::run
185
197
  it { OperationReassigningParams.run({:title => "Day Like This"}).must_equal [true, "Day Like This"] }
186
198
 
187
-
188
199
  # #invalid!(result)
189
200
  class OperationCallingInvalid < Trailblazer::Operation
190
201
  def process(params)
@@ -206,7 +217,6 @@ class OperationTest < MiniTest::Spec
206
217
 
207
218
  it { OperationCallingInvalidWithoutResult.run(true).must_equal [false, OperationCallingInvalidWithoutResult.new] }
208
219
 
209
-
210
220
  # calling return from #validate block leaves result true.
211
221
  class OperationUsingReturnInValidate < Trailblazer::Operation
212
222
  self.contract_class = class Contract
@@ -229,7 +239,6 @@ class OperationTest < MiniTest::Spec
229
239
  it { OperationUsingReturnInValidate.run(true).must_equal [true, 1] }
230
240
  it { OperationUsingReturnInValidate.run(false).must_equal [false, 2] }
231
241
 
232
-
233
242
  # unlimited arguments for ::run and friends.
234
243
  class OperationReceivingLottaArguments < Trailblazer::Operation
235
244
  def process(model, params)
@@ -239,7 +248,6 @@ class OperationTest < MiniTest::Spec
239
248
 
240
249
  it { OperationReceivingLottaArguments.run(Object, {}).must_equal([true, [Object, {}]]) }
241
250
 
242
-
243
251
  # ::present only runs #setup! which runs #model!.
244
252
  class ContractOnlyOperation < Trailblazer::Operation
245
253
  self.contract_class = class Contract
@@ -260,7 +268,6 @@ class OperationTest < MiniTest::Spec
260
268
  end
261
269
 
262
270
  it { ContractOnlyOperation.present({}).contract._model.must_equal Object }
263
-
264
271
  end
265
272
 
266
273
  class OperationBuilderTest < MiniTest::Spec
@@ -332,4 +339,4 @@ class OperationInheritanceTest < MiniTest::Spec
332
339
  song.genre.must_equal "Punkrock"
333
340
  song.band.must_equal nil
334
341
  end
335
- end
342
+ end
@@ -124,7 +124,6 @@ class ControllerRespondTest < ActionController::TestCase
124
124
  end
125
125
  end
126
126
 
127
-
128
127
  class ResponderRunTest < ActionController::TestCase
129
128
  tests BandsController
130
129
 
@@ -171,7 +170,6 @@ class ControllerPresentTest < ActionController::TestCase
171
170
  end
172
171
  end
173
172
 
174
-
175
173
  # #form.
176
174
  class ControllerFormTest < ActionController::TestCase
177
175
  tests BandsController
@@ -194,4 +192,15 @@ class ControllerFormTest < ActionController::TestCase
194
192
 
195
193
  assert_select "b", ",Band,true,Band::Create::Admin"
196
194
  end
197
- end
195
+ end
196
+
197
+ class ActiveRecordPresentTest < ActionController::TestCase
198
+ tests ActiveRecordBandsController
199
+
200
+ test "#present" do
201
+ band = Band::Create[band: {name: "Nofx"}].model
202
+ get :show, id: band.id
203
+
204
+ assert_equal "active_record_bands/show.html: Band, Band, true, Band::Update", response.body
205
+ end
206
+ end
@@ -102,4 +102,17 @@ private
102
102
  params[:band] ||= {}
103
103
  params[:band][:locality] = "Essen"
104
104
  end
105
- end
105
+ end
106
+
107
+ require 'trailblazer/operation/controller/active_record'
108
+ class ActiveRecordBandsController < ApplicationController
109
+ include Trailblazer::Operation::Controller
110
+ include Trailblazer::Operation::Controller::ActiveRecord
111
+ respond_to :html
112
+
113
+ def show
114
+ present Band::Update
115
+
116
+ render text: "active_record_bands/show.html: #{@model.class}, #{@band.class}, #{@form.is_a?(Reform::Form)}, #{@operation.class}"
117
+ end
118
+ end
@@ -33,6 +33,8 @@ app.routes.draw do
33
33
  end
34
34
  end
35
35
 
36
+ resources :active_record_bands
37
+
36
38
  resources :bands do
37
39
  collection do
38
40
  post :create
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: 0.2.0
4
+ version: 0.2.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-01-14 00:00:00.000000000 Z
11
+ date: 2015-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -181,6 +181,7 @@ files:
181
181
  - lib/trailblazer/autoloading.rb
182
182
  - lib/trailblazer/operation.rb
183
183
  - lib/trailblazer/operation/controller.rb
184
+ - lib/trailblazer/operation/controller/active_record.rb
184
185
  - lib/trailblazer/operation/crud.rb
185
186
  - lib/trailblazer/operation/representer.rb
186
187
  - lib/trailblazer/operation/responder.rb