trailblazer 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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