trailblazer 0.3.1 → 0.3.2

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: 06e8559ba04b20c80f460c00d2fe76e94a7dceae
4
- data.tar.gz: d16ed4ac3f269404152c316e2abcad2053c9f29c
3
+ metadata.gz: 6fe908e23b8ef76962d2eba061d666567e204120
4
+ data.tar.gz: 3df42356be844b922d258de9f3e64ed72f5b5a9c
5
5
  SHA512:
6
- metadata.gz: 2818fb4a0c7cfdc66b700df7feed6ac8a60dca747922ac901c311c92dffaf44dd0b3881ddfb46bf3eceebbc122d558bbd5c1acfc463a4ff2246824f519e475ee
7
- data.tar.gz: 39daeb303ccadc8dd261cfa44576bf046d94174568abecceed193de66c902cd3fc1a5ce27d99814644f26585b800d7f7568b094f5c5ff044eb8fbbaf29ea1fa6
6
+ metadata.gz: 9190472c21be262f4a216f52180c9b91bbf9c073650148fe53aec8b83ba2493f0365d4d10ebd603daa4a9f6c295411a634f66b302e7d5895c6fe8fdca66783b4
7
+ data.tar.gz: 119fbafbce0d79e95c9f4131400ba6860e12fe40f8e9e0901c4457da3450c0940915d9bb51e3ac0f502fd00cd6022224e4d082041dec843bf878537cdb3b6e71
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.3.2
2
+
3
+ * Allow to use `#contract` before `#validate`. The contract will be instantiated once per `#run` using `#contract` and then memoized. This allows to add/modify the contract _before_ you validate it using `#validate`.
4
+ * New signature for `Operation#contract_for(model, contract_class)`. It used to be contract, then model.
5
+
1
6
  # 0.3.1
2
7
 
3
8
  * Autoload `Dispatch`.
data/Gemfile CHANGED
@@ -8,4 +8,4 @@ gemspec
8
8
  # gem "disposable", path: "../disposable"
9
9
  gem "virtus"
10
10
  # gem "reform", github: "apotonick/reform"
11
- gem "reform", ">= 2.0.0.rc1"
11
+ gem "reform", "~> 2.0.0"
data/README.md CHANGED
@@ -420,6 +420,76 @@ Comment::Create.run(params) do |op|
420
420
  end
421
421
  ```
422
422
 
423
+ ### Inheritance
424
+
425
+ Operations fully support inheritance and will copy the contract, the callback groups and any methods from the original operation class.
426
+
427
+ ```ruby
428
+ class Create < Trailblazer::Operation
429
+ contract do
430
+ property :title
431
+ end
432
+
433
+ callback(:before_save) do
434
+ on_change :notify!
435
+ end
436
+
437
+ def notify!(*)
438
+ end
439
+ end
440
+ ```
441
+
442
+ This happens with normal Ruby inheritance.
443
+
444
+ ```ruby
445
+ class Update < Create
446
+ # inherited:
447
+ # contract
448
+ # callback(:before_save)
449
+ # def notify!(*)
450
+ end
451
+ ```
452
+
453
+ You can customize callback groups and contracts using the `:inherit` option, add and remove properties or add methods.
454
+
455
+ [Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
456
+
457
+ ### Modules
458
+
459
+ In case inheritance is not enough for you, use modules to share common functionality.
460
+
461
+ ```ruby
462
+ module ExtendedCreate
463
+ include Trailblazer::Operation::Module
464
+
465
+ contract do
466
+ property :id
467
+ end
468
+
469
+ callback do
470
+ on_update :update!
471
+ end
472
+
473
+ def update!(song)
474
+ # do something
475
+ end
476
+ end
477
+ ```
478
+
479
+ Modules can be included and will simply run the declarations in the including class.
480
+
481
+ ```ruby
482
+ class Create::Admin < Create
483
+ include ExtendedCreate
484
+
485
+ # contract has :title and :id now.
486
+ ```
487
+
488
+ Modules are often used to modify an existing operation for admin or signed-in roles.
489
+
490
+ [Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
491
+
492
+
423
493
  ## Form API
424
494
 
425
495
  Usually, an operation has a form object.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ default_task = Rake::Task[:build]
6
6
 
7
7
  Rake::TestTask.new(:test) do |test|
8
8
  test.libs << 'test'
9
- test.test_files = FileList['test/*_test.rb']
9
+ test.test_files = FileList['test/*_test.rb', "test/operation/*_test.rb"]
10
10
  test.verbose = true
11
11
  end
12
12
 
@@ -4,4 +4,5 @@ Trailblazer::Operation.class_eval do
4
4
  autoload :CRUD, "trailblazer/operation/crud"
5
5
  autoload :Collection, "trailblazer/operation/collection"
6
6
  autoload :Dispatch, "trailblazer/operation/dispatch"
7
+ autoload :Module, "trailblazer/operation/module"
7
8
  end
@@ -1,10 +1,6 @@
1
- require 'uber/builder'
2
- # TODO: extract all reform-contract stuff into optional module.
3
- require 'reform'
1
+ require "uber/builder"
2
+ require "reform"
4
3
 
5
- # TODO: OP[] without wrapper, OP.run with (eg for params)
6
-
7
- # to be documented: only implement #setup! when not using CRUD and when ::present needed (make example for simple Op without #setup!)
8
4
 
9
5
  module Trailblazer
10
6
  class Operation
@@ -70,12 +66,10 @@ module Trailblazer
70
66
 
71
67
  def present(*params)
72
68
  setup!(*params)
73
-
74
- @contract = contract_for(nil, @model)
69
+ contract!
75
70
  self
76
71
  end
77
72
 
78
- attr_reader :contract
79
73
  attr_reader :model
80
74
 
81
75
  def errors
@@ -86,6 +80,10 @@ module Trailblazer
86
80
  @valid
87
81
  end
88
82
 
83
+ def contract(*args)
84
+ contract!(*args)
85
+ end
86
+
89
87
  private
90
88
  def setup!(*params)
91
89
  setup_params!(*params)
@@ -105,8 +103,8 @@ module Trailblazer
105
103
  def setup_params!(*params)
106
104
  end
107
105
 
108
- def validate(params, model, contract_class=nil)
109
- @contract = contract_for(contract_class, model)
106
+ def validate(params, model=nil, contract_class=nil)
107
+ contract!(model, contract_class)
110
108
 
111
109
  if @valid = validate_contract(params)
112
110
  yield contract if block_given?
@@ -133,8 +131,15 @@ module Trailblazer
133
131
 
134
132
  # Instantiate the contract, either by using the user's contract passed into #validate
135
133
  # or infer the Operation contract.
136
- def contract_for(contract_class, *model)
137
- (contract_class || self.class.contract_class).new(*model)
134
+ def contract_for(model=nil, contract_class=nil)
135
+ model ||= self.model
136
+ contract_class ||= self.class.contract_class
137
+
138
+ contract_class.new(model)
139
+ end
140
+
141
+ def contract!(*args)
142
+ @contract ||= contract_for(*args)
138
143
  end
139
144
 
140
145
  class InvalidContract < RuntimeError
@@ -143,9 +148,3 @@ module Trailblazer
143
148
  end
144
149
 
145
150
  require 'trailblazer/operation/crud'
146
-
147
- # run
148
- # setup
149
- # process
150
- # contract
151
- # validate
@@ -12,9 +12,7 @@ private
12
12
  yield @operation if block_given?
13
13
  end
14
14
 
15
- # Doesn't run #validate.
16
- # TODO: allow only_setup.
17
- # TODO: dependency to CRUD (::model_name)
15
+ # Doesn't run #process.
18
16
  def present(operation_class, params=self.params)
19
17
  res, op = operation!(operation_class, params) { [true, operation_class.present(params)] }
20
18
 
@@ -0,0 +1,29 @@
1
+ module Trailblazer::Operation::Module
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ base.extend Included
5
+ end
6
+
7
+ module Included # TODO: use representable's inheritance mechanism.
8
+ def included(base)
9
+ super
10
+ instructions.each { |cfg|
11
+ method = cfg[0]
12
+ args = cfg[1].dup
13
+ block = cfg[2]
14
+ # options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
15
+
16
+ base.send(method, *args, &block) } # property :name, {} do .. end
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ def method_missing(method, *args, &block)
22
+ instructions << [method, args, block]
23
+ end
24
+
25
+ def instructions
26
+ @instructions ||= []
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -0,0 +1,93 @@
1
+ require 'test_helper'
2
+ require "trailblazer/operation/module"
3
+ require "trailblazer/operation/dispatch"
4
+
5
+ class OperationModuleTest < MiniTest::Spec
6
+ Song = Struct.new(:name, :artist)
7
+ Artist = Struct.new(:id, :full_name)
8
+
9
+ class Create < Trailblazer::Operation
10
+ include Trailblazer::Operation::Dispatch
11
+
12
+ contract do
13
+ property :name
14
+ property :artist, populate_if_empty: Artist do
15
+ property :id
16
+ end
17
+ end
18
+
19
+ callback do
20
+ on_change :notify_me!
21
+ end
22
+
23
+ def process(params)
24
+ @model = Song.new
25
+
26
+ validate(params, @model) do
27
+ contract.sync
28
+
29
+ dispatch!
30
+ end
31
+ end
32
+
33
+ def dispatched
34
+ @dispatched ||= []
35
+ end
36
+
37
+ private
38
+ def notify_me!(*)
39
+ dispatched << :notify_me!
40
+ end
41
+ end
42
+
43
+
44
+ module SignedIn
45
+ include Trailblazer::Operation::Module
46
+
47
+ contract do
48
+ property :artist, inherit: true do
49
+ property :full_name
50
+ end
51
+ end
52
+
53
+ callback do
54
+ on_change :notify_you!
55
+ end
56
+
57
+ def notify_you!(*)
58
+ dispatched << :notify_you!
59
+ end
60
+ end
61
+
62
+
63
+ class Update < Create
64
+ callback do
65
+ on_change :notify_them!
66
+ end
67
+
68
+ include SignedIn
69
+
70
+ def notify_them!(*)
71
+ dispatched << :notify_them!
72
+ end
73
+ end
74
+
75
+
76
+ it do
77
+ op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
78
+
79
+ op.dispatched.must_equal [:notify_me!]
80
+ op.model.name.must_equal "Feelings"
81
+ op.model.artist.id.must_equal 1
82
+ op.model.artist.full_name.must_equal nil # property not declared.
83
+ end
84
+
85
+ it do
86
+ op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
87
+
88
+ op.dispatched.must_equal [:notify_me!, :notify_them!, :notify_you!]
89
+ op.model.name.must_equal "Feelings"
90
+ op.model.artist.id.must_equal 1
91
+ op.model.artist.full_name.must_equal "The Offspring" # property declared via Module.
92
+ end
93
+ end
@@ -0,0 +1,30 @@
1
+ require "test_helper"
2
+
3
+
4
+ class OperationContractTest < MiniTest::Spec
5
+
6
+ class Operation < Trailblazer::Operation
7
+ contract do
8
+ property :id
9
+ property :title
10
+ property :length
11
+ end
12
+
13
+ def process(params)
14
+ @model = Struct.new(:id, :title, :length).new
15
+
16
+ contract.id = 1
17
+ validate(params) do
18
+ contract.length = 3
19
+ end
20
+ end
21
+ end
22
+
23
+ # allow using #contract before #validate.
24
+ it do
25
+ op = Operation.(title: "Beethoven")
26
+ op.contract.id.must_equal 1
27
+ op.contract.title.must_equal "Beethoven"
28
+ op.contract.length.must_equal 3
29
+ end
30
+ end
@@ -57,12 +57,9 @@ class Band < ActiveRecord::Base
57
57
  end
58
58
  end
59
59
 
60
+ require "representable/json"
60
61
  class JSON < self
61
62
  include Representer
62
- require "reform/form/json"
63
- contract do
64
- include Reform::Form::JSON # this allows deserialising JSON.
65
- end
66
63
 
67
64
  representer do
68
65
  collection :songs, inherit: true, render_empty: false # tested in ControllerPresentTest.
@@ -77,17 +74,9 @@ class Band < ActiveRecord::Base
77
74
  end
78
75
  end
79
76
 
80
- # TODO: wait for uber 0.0.10 and @dutow.
81
- # builds -> (params)
82
- # return JSON if params[:format] == "json"
83
- # return Admin if params[:admin]
84
- # end
85
- builds do |params|
86
- if params[:format] == "json"
87
- JSON
88
- elsif params[:admin]
89
- Admin
90
- end
77
+ builds -> (params) do
78
+ return JSON if params[:format] == "json"
79
+ return Admin if params[:admin]
91
80
  end
92
81
  end
93
82
 
@@ -1,4 +1,9 @@
1
1
  require 'trailblazer'
2
2
  require 'minitest/autorun'
3
3
 
4
- require 'fake_app/rails_app.rb'
4
+ require "reform/form/active_model/validations"
5
+ Reform::Form.class_eval do
6
+ include Reform::Form::ActiveModel::Validations
7
+ end
8
+
9
+ require 'fake_app/rails_app.rb'
@@ -55,3 +55,8 @@ class MiniTest::Spec
55
55
  end
56
56
  end
57
57
 
58
+
59
+ require "reform/form/active_model/validations"
60
+ Reform::Form.class_eval do
61
+ include Reform::Form::ActiveModel::Validations
62
+ end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'trailblazer/version'
@@ -26,7 +25,6 @@ Gem::Specification.new do |spec|
26
25
  spec.add_development_dependency "bundler"
27
26
  spec.add_development_dependency "rake"
28
27
  spec.add_development_dependency "minitest"
29
- # spec.add_development_dependency "minitest-spec-rails" # TODO: can anyone make this work (test/rails).
30
28
  spec.add_development_dependency "sidekiq", "~> 3.1.0"
31
29
  spec.add_development_dependency "actionpack", '>= 3.0.0' # Rails is optional.
32
30
  spec.add_development_dependency "rails"
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.3.1
4
+ version: 0.3.2
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-07-03 00:00:00.000000000 Z
11
+ date: 2015-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber
@@ -208,6 +208,7 @@ files:
208
208
  - lib/trailblazer/operation/controller/active_record.rb
209
209
  - lib/trailblazer/operation/crud.rb
210
210
  - lib/trailblazer/operation/dispatch.rb
211
+ - lib/trailblazer/operation/module.rb
211
212
  - lib/trailblazer/operation/representer.rb
212
213
  - lib/trailblazer/operation/responder.rb
213
214
  - lib/trailblazer/operation/uploaded_file.rb
@@ -219,6 +220,8 @@ files:
219
220
  - test/dispatch_test.rb
220
221
  - test/fixtures/apotomo.png
221
222
  - test/fixtures/cells.png
223
+ - test/module_test.rb
224
+ - test/operation/contract_test.rb
222
225
  - test/operation_test.rb
223
226
  - test/rails/controller_test.rb
224
227
  - test/rails/endpoint_test.rb
@@ -271,6 +274,8 @@ test_files:
271
274
  - test/dispatch_test.rb
272
275
  - test/fixtures/apotomo.png
273
276
  - test/fixtures/cells.png
277
+ - test/module_test.rb
278
+ - test/operation/contract_test.rb
274
279
  - test/operation_test.rb
275
280
  - test/rails/controller_test.rb
276
281
  - test/rails/endpoint_test.rb