trailblazer 1.1.2 → 2.0.0.beta1

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -7
  3. data/CHANGES.md +108 -0
  4. data/COMM-LICENSE +91 -0
  5. data/Gemfile +18 -4
  6. data/LICENSE.txt +7 -20
  7. data/README.md +55 -15
  8. data/Rakefile +21 -2
  9. data/draft-1.2.rb +7 -0
  10. data/lib/trailblazer.rb +17 -4
  11. data/lib/trailblazer/dsl.rb +47 -0
  12. data/lib/trailblazer/operation/auto_inject.rb +47 -0
  13. data/lib/trailblazer/operation/builder.rb +18 -18
  14. data/lib/trailblazer/operation/callback.rb +31 -38
  15. data/lib/trailblazer/operation/contract.rb +46 -0
  16. data/lib/trailblazer/operation/controller.rb +45 -27
  17. data/lib/trailblazer/operation/guard.rb +24 -0
  18. data/lib/trailblazer/operation/model.rb +41 -33
  19. data/lib/trailblazer/operation/nested.rb +43 -0
  20. data/lib/trailblazer/operation/params.rb +13 -0
  21. data/lib/trailblazer/operation/persist.rb +13 -0
  22. data/lib/trailblazer/operation/policy.rb +26 -72
  23. data/lib/trailblazer/operation/present.rb +19 -0
  24. data/lib/trailblazer/operation/procedural/contract.rb +15 -0
  25. data/lib/trailblazer/operation/procedural/validate.rb +22 -0
  26. data/lib/trailblazer/operation/pundit.rb +42 -0
  27. data/lib/trailblazer/operation/representer.rb +25 -92
  28. data/lib/trailblazer/operation/rescue.rb +23 -0
  29. data/lib/trailblazer/operation/resolver.rb +18 -24
  30. data/lib/trailblazer/operation/validate.rb +50 -0
  31. data/lib/trailblazer/operation/wrap.rb +37 -0
  32. data/lib/trailblazer/version.rb +1 -1
  33. data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
  34. data/test/docs/auto_inject_test.rb +30 -0
  35. data/test/docs/contract_test.rb +429 -0
  36. data/test/docs/dry_test.rb +31 -0
  37. data/test/docs/guard_test.rb +143 -0
  38. data/test/docs/nested_test.rb +117 -0
  39. data/test/docs/policy_test.rb +2 -0
  40. data/test/docs/pundit_test.rb +109 -0
  41. data/test/docs/representer_test.rb +268 -0
  42. data/test/docs/rescue_test.rb +153 -0
  43. data/test/docs/wrap_test.rb +174 -0
  44. data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
  45. data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
  46. data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
  47. data/test/module_test.rb +22 -15
  48. data/test/operation/builder_test.rb +66 -18
  49. data/test/operation/callback_test.rb +70 -0
  50. data/test/operation/contract_test.rb +385 -15
  51. data/test/operation/dsl/callback_test.rb +18 -30
  52. data/test/operation/dsl/contract_test.rb +209 -19
  53. data/test/operation/dsl/representer_test.rb +42 -15
  54. data/test/operation/guard_test.rb +1 -147
  55. data/test/operation/model_test.rb +105 -0
  56. data/test/operation/params_test.rb +36 -0
  57. data/test/operation/persist_test.rb +44 -0
  58. data/test/operation/pipedream_test.rb +59 -0
  59. data/test/operation/pipetree_test.rb +104 -0
  60. data/test/operation/present_test.rb +24 -0
  61. data/test/operation/pundit_test.rb +104 -0
  62. data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
  63. data/test/operation/resolver_test.rb +34 -70
  64. data/test/operation_test.rb +57 -189
  65. data/test/test_helper.rb +23 -3
  66. data/trailblazer.gemspec +8 -7
  67. metadata +91 -59
  68. data/gemfiles/Gemfile.rails.lock +0 -130
  69. data/gemfiles/Gemfile.reform-2.0 +0 -6
  70. data/gemfiles/Gemfile.reform-2.1 +0 -7
  71. data/lib/trailblazer/autoloading.rb +0 -15
  72. data/lib/trailblazer/endpoint.rb +0 -31
  73. data/lib/trailblazer/operation.rb +0 -175
  74. data/lib/trailblazer/operation/collection.rb +0 -6
  75. data/lib/trailblazer/operation/dispatch.rb +0 -3
  76. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  77. data/lib/trailblazer/operation/model/external.rb +0 -34
  78. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  79. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  80. data/test/callback_test.rb +0 -104
  81. data/test/collection_test.rb +0 -57
  82. data/test/model_test.rb +0 -148
  83. data/test/operation/external_model_test.rb +0 -71
  84. data/test/operation/policy_test.rb +0 -97
  85. data/test/operation/reject_test.rb +0 -34
  86. data/test/rollback_test.rb +0 -47
@@ -1,35 +0,0 @@
1
- module Trailblazer
2
- # Policy::Guard is a very simple policy implementation.
3
- # It adds #evaluate_policy to Operation#setup! and calls whatever
4
- # you provided to ::policy.
5
- #
6
- # http://trailblazer.to/gems/operation/policy.html#guard
7
- module Operation::Policy
8
- module Guard
9
- def self.included(includer)
10
- includer.extend(DSL) # Provides ::policy(CallableObject)
11
- includer.extend(ClassMethods)
12
- includer.send(:include, Setup)
13
- end
14
-
15
- module ClassMethods
16
- def policy(callable=nil, &block)
17
- self.policy_config = Uber::Options::Value.new(callable || block)
18
- end
19
- end
20
-
21
- def evaluate_policy(params)
22
- call_policy(params) or raise policy_exception
23
- end
24
-
25
- # Override if you want your own policy invocation, e.g. with more args.
26
- def call_policy(params)
27
- self.class.policy_config.(self, params)
28
- end
29
-
30
- def policy_exception
31
- NotAuthorizedError.new
32
- end
33
- end
34
- end
35
- end
@@ -1,77 +0,0 @@
1
- require 'trailblazer/operation'
2
- require 'action_dispatch/http/upload'
3
- require 'tempfile'
4
-
5
- module Trailblazer
6
- # TODO: document:
7
- # to_hash
8
- # from_hash
9
- # initialize/tmp_dir
10
- class Operation::UploadedFile
11
- def initialize(uploaded, options={})
12
- @uploaded = uploaded
13
- @options = options
14
- @tmp_dir = options[:tmp_dir]
15
- end
16
-
17
- def to_hash
18
- path = persist!
19
-
20
- hash = {
21
- filename: @uploaded.original_filename,
22
- type: @uploaded.content_type,
23
- tempfile_path: path
24
- }
25
-
26
- cleanup!
27
-
28
- hash
29
- end
30
-
31
- # Returns a ActionDispatch::Http::UploadedFile as if the upload was in the same request.
32
- def self.from_hash(hash)
33
- suffix = File.extname(hash[:filename])
34
-
35
- # we need to create a Tempfile to make Http::UploadedFile work.
36
- tmp = Tempfile.new(["bla", suffix]) # always force file suffix to avoid problems with imagemagick etc.
37
- file = File.open(hash[:tempfile_path])# doesn't close automatically :( # fixme: introduce strategy (Tempfile:=>slow, File:=> hopefully less memory footprint)
38
- tmp.write(file.read) # DISCUSS: We need Tempfile.new(<File>) to avoid this slow and memory-consuming mechanics.
39
-
40
- file.close # TODO: can we test that?
41
- File.unlink(file)
42
-
43
- ActionDispatch::Http::UploadedFile.new(hash.merge(tempfile: tmp))
44
- end
45
-
46
- private
47
- attr_reader :tmp_dir
48
-
49
- # convert Tempfile from Rails upload into persistent "temp" file so it is available in workers.
50
- def persist!
51
- path = @uploaded.path # original Tempfile path (from Rails).
52
- path = path_with_tmp_dir(path)
53
-
54
- path = path + "_trailblazer_upload"
55
-
56
- FileUtils.mv(@uploaded.path, path) # move Rails upload file into persistent `path`.
57
- path
58
- end
59
-
60
- def path_with_tmp_dir(path)
61
- return path unless tmp_dir # if tmp_dir set, create path in it.
62
-
63
- @with_tmp_dir = Tempfile.new(File.basename(path), tmp_dir)
64
- @with_tmp_dir.path # use Tempfile to create nested dirs (os-dependent.)
65
- end
66
-
67
- def delete!(file)
68
- file.close
69
- file.unlink # the Rails uploaded file is already unlinked since moved.
70
- end
71
-
72
- def cleanup!
73
- delete!(@uploaded.tempfile) if @uploaded.respond_to?(:tempfile) # this is Rails' uploaded file, not sure if we need to do that. in 3.2, we don't have UploadedFile#close, yet.
74
- delete!(@with_tmp_dir) if @with_tmp_dir # we used that file to create a tmp file path below tmp_dir.
75
- end
76
- end
77
- end
@@ -1,104 +0,0 @@
1
- require 'test_helper'
2
- require "trailblazer/operation/callback"
3
-
4
- # callbacks are tested in Disposable::Callback::Group.
5
- class OperationCallbackTest < MiniTest::Spec
6
- Song = Struct.new(:name)
7
-
8
- class Create < Trailblazer::Operation
9
- include Trailblazer::Operation::Callback
10
-
11
- contract do
12
- property :name
13
- end
14
-
15
- callback do
16
- on_change :notify_me!
17
- on_change :notify_you!
18
- end
19
-
20
- # TODO: always dispatch, pass params.
21
-
22
- def process(params)
23
- @model = Song.new
24
-
25
- validate(params, @model) do
26
- callback!
27
- end
28
- end
29
-
30
- def dispatched
31
- @dispatched ||= []
32
- end
33
-
34
- private
35
- def notify_me!(*)
36
- dispatched << :notify_me!
37
- end
38
-
39
- def notify_you!(*)
40
- dispatched << :notify_you!
41
- end
42
- end
43
-
44
-
45
- class Update < Create
46
- # TODO: allow skipping groups.
47
- # skip_dispatch :notify_me!
48
-
49
- callback do
50
- remove! :on_change, :notify_me!
51
- end
52
- end
53
-
54
-
55
- it "invokes all callbacks" do
56
- op = Create.({"name"=>"Keep On Running"})
57
- op.dispatched.must_equal [:notify_me!, :notify_you!]
58
- end
59
-
60
- it "does not invoke removed callbacks" do
61
- op = Update.({"name"=>"Keep On Running"})
62
- op.dispatched.must_equal [:notify_you!]
63
- end
64
- end
65
-
66
- # TODO: remove in 1.2.
67
- require "trailblazer/operation/dispatch"
68
- class OperationDeprecatedDispatchTest < MiniTest::Spec
69
- Song = Struct.new(:name)
70
-
71
- class Create < Trailblazer::Operation
72
- include Trailblazer::Operation::Dispatch
73
-
74
- contract do
75
- property :name
76
- end
77
-
78
- callback do
79
- on_change :notify_me!
80
- end
81
-
82
- def process(params)
83
- @model = Song.new
84
-
85
- validate(params, @model) do
86
- dispatch!
87
- end
88
- end
89
-
90
- def dispatched
91
- @dispatched ||= []
92
- end
93
-
94
- private
95
- def notify_me!(*)
96
- dispatched << :notify_me!
97
- end
98
- end
99
-
100
- it "invokes all callbacks [deprecated]" do
101
- op = Create.({"name"=>"Keep On Running"})
102
- op.dispatched.must_equal [:notify_me!]
103
- end
104
- end
@@ -1,57 +0,0 @@
1
- require "test_helper"
2
- require "trailblazer/operation/collection"
3
- require "trailblazer/operation/model"
4
-
5
- class CollectionTest < MiniTest::Spec
6
- Song = Struct.new(:title, :id) do
7
- class << self
8
- attr_accessor :all_records
9
-
10
- def all
11
- all_records
12
- end
13
- end
14
- end
15
-
16
-
17
- class CreateOperation < Trailblazer::Operation
18
- include Model
19
- model Song
20
- action :create
21
-
22
- contract do
23
- property :title
24
- validates :title, presence: true
25
- end
26
-
27
- def process(params)
28
- validate(params[:song]) do |f|
29
- f.sync
30
- end
31
- end
32
- end
33
-
34
- class FetchCollectionOperation < CreateOperation
35
- include Trailblazer::Operation::Collection
36
-
37
- model Song
38
-
39
- contract do
40
- property :title
41
- end
42
-
43
- def model!(params)
44
- Song.all
45
- end
46
- end
47
-
48
- # ::present.
49
- it do
50
- Song.all_records = [
51
- CreateOperation.(song: {title: "Blue Rondo a la Turk"}).model,
52
- CreateOperation.(song: {title: "Mercy Day For Mr. Vengeance"}).model
53
- ]
54
- op = FetchCollectionOperation.present(user_id: 0)
55
- op.model.must_equal Song.all_records
56
- end
57
- end
data/test/model_test.rb DELETED
@@ -1,148 +0,0 @@
1
- require 'test_helper'
2
- require 'trailblazer/operation'
3
-
4
- class ModelTest < MiniTest::Spec
5
- Song = Struct.new(:title, :id) do
6
- class << self
7
- attr_accessor :find_result # TODO: eventually, replace with AR test.
8
- attr_accessor :all_records
9
-
10
- def find(id)
11
- find_result
12
- end
13
- end
14
- end
15
-
16
- class CreateOperation < Trailblazer::Operation
17
- include Model
18
- model Song
19
- action :create
20
-
21
- contract do
22
- property :title
23
- validates :title, presence: true
24
- end
25
-
26
- def process(params)
27
- validate(params[:song]) do |f|
28
- f.sync
29
- end
30
- end
31
- end
32
-
33
-
34
- # creates model for you.
35
- it { CreateOperation.(song: {title: "Blue Rondo a la Turk"}).model.title.must_equal "Blue Rondo a la Turk" }
36
- # exposes #model.
37
- it { CreateOperation.(song: {title: "Blue Rondo a la Turk"}).model.must_be_instance_of Song }
38
-
39
- class ModifyingCreateOperation < CreateOperation
40
- def process(params)
41
- model.instance_eval { def genre; "Punkrock"; end }
42
-
43
- validate(params[:song]) do |f|
44
- f.sync
45
- end
46
- end
47
- end
48
-
49
- # lets you modify model.
50
- it { ModifyingCreateOperation.(song: {title: "Blue Rondo a la Turk"}).model.title.must_equal "Blue Rondo a la Turk" }
51
- it { ModifyingCreateOperation.(song: {title: "Blue Rondo a la Turk"}).model.genre.must_equal "Punkrock" }
52
-
53
- # Update
54
- class UpdateOperation < CreateOperation
55
- action :update
56
- end
57
-
58
- # finds model and updates.
59
- it do
60
- song = CreateOperation.(song: {title: "Anchor End"}).model
61
- Song.find_result = song
62
-
63
- UpdateOperation.(id: song.id, song: {title: "The Rip"}).model.title.must_equal "The Rip"
64
- song.title.must_equal "The Rip"
65
- end
66
-
67
- # Find == Update
68
- class FindOperation < CreateOperation
69
- action :find
70
- end
71
-
72
- # finds model and updates.
73
- it do
74
- song = CreateOperation.(song: {title: "Anchor End"}).model
75
- Song.find_result = song
76
-
77
- FindOperation.(id: song.id, song: {title: "The Rip"}).model.title.must_equal "The Rip"
78
- song.title.must_equal "The Rip"
79
- end
80
-
81
-
82
- class DefaultCreateOperation < Trailblazer::Operation
83
- include Model
84
- model Song
85
-
86
- def process(params)
87
- self
88
- end
89
- end
90
-
91
- # uses :create as default if not set via ::action.
92
- it { DefaultCreateOperation.({}).model.must_equal Song.new }
93
-
94
- # model Song, :action
95
- class ModelUpdateOperation < CreateOperation
96
- model Song, :update
97
- end
98
-
99
- # allows ::model, :action.
100
- it do
101
- Song.find_result = song = Song.new
102
- ModelUpdateOperation.({id: 1, song: {title: "Mercy Day For Mr. Vengeance"}}).model.must_equal song
103
- end
104
-
105
-
106
-
107
- # Op#setup_model!
108
- class SetupModelOperation < CreateOperation
109
- def setup_model!(params)
110
- model.instance_eval { @params = params; def params; @params.to_s; end }
111
- end
112
- end
113
-
114
- it { SetupModelOperation.(song: {title: "Emily Kane"}).model.params.must_equal "{:song=>{:title=>\"Emily Kane\"}}" }
115
-
116
-
117
-
118
- # no call to ::model raises error.
119
- class NoModelOperation < Trailblazer::Operation
120
- include Model
121
-
122
- def process(params)
123
- self
124
- end
125
- end
126
-
127
- # uses :create as default if not set via ::action.
128
- it { assert_raises(RuntimeError){ NoModelOperation.({}) } }
129
-
130
- # allow passing validate(params, model, contract_class)
131
- class OperationWithPrivateContract < Trailblazer::Operation
132
- include Model
133
- model Song
134
-
135
- class Contract < Reform::Form
136
- property :title
137
- end
138
-
139
- def process(params)
140
- validate(params[:song], model, Contract) do |f|
141
- f.sync
142
- end
143
- end
144
- end
145
-
146
- # uses private Contract class.
147
- it { OperationWithPrivateContract.(song: {title: "Blue Rondo a la Turk"}).model.title.must_equal "Blue Rondo a la Turk" }
148
- end
@@ -1,71 +0,0 @@
1
- require "test_helper"
2
- require "trailblazer/operation/model/external"
3
-
4
- class ExternalModelTest < MiniTest::Spec
5
- Song = Struct.new(:title, :id) do
6
- class << self
7
- attr_accessor :find_result # TODO: eventually, replace with AR test.
8
- attr_accessor :all_records
9
-
10
- def find(id)
11
- find_result.tap do |song|
12
- song.id = id
13
- end
14
- end
15
- end
16
- end # FIXME: use from CrudTest.
17
-
18
-
19
-
20
-
21
- class Bla < Trailblazer::Operation
22
- include Model::External
23
- model Song, :update
24
-
25
- def process(params)
26
- end
27
- end
28
-
29
- let (:song) { Song.new("Numbers") }
30
-
31
- before do
32
- Song.find_result = song
33
- end
34
-
35
- # ::model!
36
- it do
37
- Bla.model!(id: 1).must_equal song
38
- song.id.must_equal 1
39
- end
40
-
41
- # call style.
42
- it do
43
- Bla.(id: 2).model.must_equal song
44
- song.id.must_equal 2
45
- end
46
-
47
- # #present.
48
- it do
49
- Bla.present({}).model.must_equal song
50
- end
51
-
52
-
53
- class OpWithBuilder < Bla
54
- class A < self
55
- end
56
-
57
- builds -> (model, params) do
58
- return A if model.id == 1 and params[:user] == 2
59
- end
60
- end
61
-
62
- describe "::builds args" do
63
- it do
64
- OpWithBuilder.(id: 1, user: "different").must_be_instance_of OpWithBuilder
65
- end
66
-
67
- it do
68
- OpWithBuilder.(id: 1, user: 2).must_be_instance_of OpWithBuilder::A
69
- end
70
- end
71
- end