trailblazer 1.1.2 → 2.0.0.beta1

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