trailblazer 1.1.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  4. data/.rubocop.yml +20 -0
  5. data/.rubocop_todo.yml +556 -0
  6. data/.travis.yml +6 -7
  7. data/CHANGES.md +224 -0
  8. data/COMM-LICENSE +62 -0
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -10
  11. data/LICENSE +9 -0
  12. data/README.md +68 -189
  13. data/Rakefile +3 -3
  14. data/lib/trailblazer/version.rb +3 -1
  15. data/lib/trailblazer.rb +3 -6
  16. data/test/test_helper.rb +32 -3
  17. data/trailblazer.gemspec +11 -15
  18. metadata +28 -132
  19. data/LICENSE.txt +0 -22
  20. data/TODO.md +0 -11
  21. data/doc/Trb-The-Stack.png +0 -0
  22. data/doc/trb.jpg +0 -0
  23. data/gemfiles/Gemfile.rails.lock +0 -130
  24. data/gemfiles/Gemfile.reform-2.0 +0 -6
  25. data/gemfiles/Gemfile.reform-2.1 +0 -7
  26. data/lib/trailblazer/autoloading.rb +0 -15
  27. data/lib/trailblazer/endpoint.rb +0 -31
  28. data/lib/trailblazer/operation/builder.rb +0 -26
  29. data/lib/trailblazer/operation/callback.rb +0 -53
  30. data/lib/trailblazer/operation/collection.rb +0 -6
  31. data/lib/trailblazer/operation/controller.rb +0 -72
  32. data/lib/trailblazer/operation/dispatch.rb +0 -3
  33. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  34. data/lib/trailblazer/operation/model/external.rb +0 -34
  35. data/lib/trailblazer/operation/model.rb +0 -50
  36. data/lib/trailblazer/operation/module.rb +0 -29
  37. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  38. data/lib/trailblazer/operation/policy.rb +0 -85
  39. data/lib/trailblazer/operation/representer.rb +0 -98
  40. data/lib/trailblazer/operation/resolver.rb +0 -30
  41. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  42. data/lib/trailblazer/operation.rb +0 -175
  43. data/test/callback_test.rb +0 -104
  44. data/test/collection_test.rb +0 -57
  45. data/test/model_test.rb +0 -148
  46. data/test/module_test.rb +0 -93
  47. data/test/operation/builder_test.rb +0 -41
  48. data/test/operation/contract_test.rb +0 -30
  49. data/test/operation/controller_test.rb +0 -111
  50. data/test/operation/dsl/callback_test.rb +0 -118
  51. data/test/operation/dsl/contract_test.rb +0 -104
  52. data/test/operation/dsl/representer_test.rb +0 -142
  53. data/test/operation/external_model_test.rb +0 -71
  54. data/test/operation/guard_test.rb +0 -152
  55. data/test/operation/policy_test.rb +0 -97
  56. data/test/operation/reject_test.rb +0 -34
  57. data/test/operation/resolver_test.rb +0 -83
  58. data/test/operation_test.rb +0 -275
  59. data/test/representer_test.rb +0 -238
  60. data/test/rollback_test.rb +0 -47
@@ -1,34 +0,0 @@
1
- require "trailblazer/operation/model"
2
-
3
- class Trailblazer::Operation
4
- module Model
5
- # Builds (finds or creates) the model _before_ the operation is instantiated.
6
- # Passes the model instance into the builder with the following signature.
7
- #
8
- # builds ->(model, params)
9
- #
10
- # The initializer will now expect you to pass the model in via options[:model]. This
11
- # happens automatically when coming from a builder.
12
- module External
13
- def self.included(includer)
14
- includer.extend Model::DSL
15
- includer.extend Model::BuildModel
16
- includer.extend ClassMethods
17
- end
18
-
19
- def assign_model!(*) # i don't like to "disable" the `@model =` like this but it's the simplest for now.
20
- @model = @options[:model]
21
- end
22
-
23
-
24
- module ClassMethods
25
- private
26
- def build_operation(params, options={}) # TODO: merge with Resolver::build_operation.
27
- model = model!(params)
28
- build_operation_class(model, params). # calls builds->(model, params).
29
- new(params, options.merge(model: model))
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,50 +0,0 @@
1
- require "trailblazer/operation/model/dsl"
2
-
3
- module Trailblazer
4
- class Operation
5
- # The Model module will automatically create/find models for the configured +action+.
6
- # It adds a public +Operation#model+ reader to access the model (after performing).
7
- module Model
8
- def self.included(base)
9
- base.extend DSL
10
- end
11
-
12
- # Methods to create the model according to class configuration and params.
13
- module BuildModel
14
- def model!(params)
15
- instantiate_model(params)
16
- end
17
-
18
- def instantiate_model(params)
19
- send("#{action_name}_model", params)
20
- end
21
-
22
- def create_model(params)
23
- model_class.new
24
- end
25
-
26
- def update_model(params)
27
- model_class.find(params[:id])
28
- end
29
-
30
- alias_method :find_model, :update_model
31
- end
32
-
33
-
34
- # #validate no longer accepts a model since this module instantiates it for you.
35
- def validate(params, model=self.model, *args)
36
- super(params, model, *args)
37
- end
38
-
39
- private
40
- include BuildModel
41
-
42
- def model_class
43
- self.class.model_class
44
- end
45
- def action_name
46
- self.class.action_name
47
- end
48
- end
49
- end
50
- end
@@ -1,29 +0,0 @@
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,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,85 +0,0 @@
1
- require "trailblazer/operation/policy/guard"
2
-
3
- module Trailblazer
4
- class NotAuthorizedError < RuntimeError
5
- end
6
-
7
- # Adds #evaluate_policy to #setup!, and ::policy.
8
- module Operation::Policy
9
- def self.included(includer)
10
- includer.extend DSL
11
- end
12
-
13
- module DSL
14
- def self.extended(extender)
15
- extender.inheritable_attr :policy_config
16
- extender.policy_config = lambda { |*| true } # return true per default.
17
- end
18
-
19
- def policy(*args, &block)
20
- self.policy_config = permission_class.new(*args, &block)
21
- end
22
-
23
- def permission_class
24
- Permission
25
- end
26
- end
27
-
28
- attr_reader :policy
29
-
30
- private
31
- module Setup
32
- def setup!(params)
33
- evaluate_policy(super)
34
- end
35
- end
36
- include Setup
37
-
38
- def evaluate_policy(params)
39
- user = params[:current_user]
40
-
41
- @policy = self.class.policy_config.(user, model, @policy) do |policy, action|
42
- raise policy_exception(policy, action, model)
43
- end
44
- end
45
-
46
- def policy_exception(policy, action, model)
47
- NotAuthorizedError.new(query: action, record: model, policy: policy)
48
- end
49
-
50
- # Encapsulate building the Policy object and calling the defined query action.
51
- # This assumes the policy class is "pundit-style", as in Policy.new(user, model).edit?.
52
- class Permission
53
- def initialize(policy_class, action)
54
- @policy_class, @action = policy_class, action
55
- end
56
-
57
- # Without a block, return the policy object (which is usually a Pundit-style class).
58
- # When block is passed evaluate the default rule and run block when false.
59
- def call(user, model, external_policy=nil)
60
- policy = build_policy(user, model, external_policy)
61
-
62
- policy.send(@action) || yield(policy, @action) if block_given?
63
- policy
64
- end
65
-
66
- private
67
- def build_policy(user, model, policy)
68
- policy or @policy_class.new(user, model)
69
- end
70
- end
71
- end
72
-
73
-
74
- module Operation::Deny
75
- def self.included(includer)
76
- includer.extend ClassMethods
77
- end
78
-
79
- module ClassMethods
80
- def deny!
81
- raise NotAuthorizedError
82
- end
83
- end
84
- end
85
- end
@@ -1,98 +0,0 @@
1
- # Including this will change the way deserialization in #validate works.
2
- #
3
- # Instead of treating params as a hash and letting the form object deserialize it,
4
- # a representer will be infered from the contract. This representer is then passed as
5
- # deserializer into Form#validate.
6
- #
7
- # TODO: so far, we only support JSON, but it's two lines to change to support any kind of format.
8
- module Trailblazer::Operation::Representer
9
- def self.included(base)
10
- base.extend DSL
11
- end
12
-
13
- module DSL
14
- def self.extended(extender)
15
- extender.inheritable_attr :_representer_class
16
- end
17
-
18
- def representer(constant=nil, &block)
19
- return representer_class unless constant or block_given?
20
-
21
- self.representer_class= Class.new(constant) if constant
22
- representer_class.class_eval(&block) if block_given?
23
- end
24
-
25
- def representer_class
26
- self._representer_class ||= infer_representer_class
27
- end
28
-
29
- def representer_class=(constant)
30
- self._representer_class = constant
31
- end
32
-
33
- require "disposable/version"
34
- def infer_representer_class
35
- if Disposable::VERSION =~ /^0.1/
36
- warn "[Trailblazer] Reform 2.0 won't be supported in Trailblazer 1.2. Don't be lazy and upgrade to Reform 2.1."
37
-
38
- Disposable::Twin::Schema.from(contract_class,
39
- include: [Representable::JSON],
40
- options_from: :deserializer, # use :instance etc. in deserializer.
41
- superclass: Representable::Decorator,
42
- representer_from: lambda { |inline| inline.representer_class },
43
- )
44
- else
45
- Disposable::Rescheme.from(contract_class,
46
- include: [Representable::JSON],
47
- options_from: :deserializer, # use :instance etc. in deserializer.
48
- superclass: Representable::Decorator,
49
- definitions_from: lambda { |inline| inline.definitions },
50
- exclude_options: [:default, :populator], # TODO: test with populator: in an operation.
51
- exclude_properties: [:persisted?]
52
- )
53
- end
54
- end
55
- end
56
-
57
- private
58
- module Rendering
59
- # Override this if you need to pass options to the rendering.
60
- #
61
- # def to_json(*)
62
- # super(include: @params[:include])
63
- # end
64
- def to_json(options={})
65
- self.class.representer_class.new(represented).to_json(options)
66
- end
67
-
68
- # Override this if you want to render something else, e.g. the contract.
69
- def represented
70
- model
71
- end
72
- end
73
- include Rendering
74
-
75
-
76
- module Deserializer
77
- module Hash
78
- def validate_contract(params)
79
- # use the inferred representer from the contract for deserialization in #validate.
80
- contract.validate(params) do |document|
81
- self.class.representer_class.new(contract).from_hash(document)
82
- end
83
- end
84
- end
85
-
86
- # This looks crazy, but all it does is using a Reform hook in #validate where we can use
87
- # our own representer for deserialization. After the object graph is set up, Reform will
88
- # run its validation without even knowing this came from JSON.
89
- module JSON
90
- def validate_contract(params)
91
- contract.validate(params) do |document|
92
- self.class.representer_class.new(contract).from_json(document)
93
- end
94
- end
95
- end
96
- end
97
- include Deserializer::JSON
98
- end
@@ -1,30 +0,0 @@
1
- require "trailblazer/operation/model/external"
2
- require "trailblazer/operation/policy"
3
-
4
- class Trailblazer::Operation
5
- # Provides builds-> (model, policy, params).
6
- module Resolver
7
- def self.included(includer)
8
- includer.class_eval do
9
- include Policy # ::build_policy
10
- include Model::External # ::build_operation_class
11
-
12
- extend BuildOperation # ::build_operation
13
- end
14
- end
15
-
16
- module BuildOperation
17
- def build_operation(params, options={})
18
- model = model!(params)
19
- policy = policy_config.call(params[:current_user], model)
20
- build_operation_class(model, policy, params).
21
- new(params, options.merge(model: model, policy: policy))
22
- end
23
- end
24
-
25
- def initialize(params, options)
26
- @policy = options[:policy]
27
- super
28
- end
29
- end
30
- 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,175 +0,0 @@
1
- require "reform"
2
-
3
- module Trailblazer
4
- class Operation
5
- require "trailblazer/operation/builder"
6
- extend Builder # imports ::builder_class and ::build_operation.
7
-
8
- extend Uber::InheritableAttr
9
- inheritable_attr :contract_class
10
- self.contract_class = Reform::Form.clone
11
-
12
- class << self
13
- def run(params, &block) # Endpoint behaviour
14
- res, op = build_operation(params).run
15
-
16
- if block_given?
17
- yield op if res
18
- return op
19
- end
20
-
21
- [res, op]
22
- end
23
-
24
- # Like ::run, but yield block when invalid.
25
- def reject(*args)
26
- res, op = run(*args)
27
- yield op if res == false
28
- op
29
- end
30
-
31
- # ::call only returns the Operation instance (or whatever was returned from #validate).
32
- # This is useful in tests or in irb, e.g. when using Op as a factory and you already know it's valid.
33
- def call(params)
34
- build_operation(params, raise_on_invalid: true).run.last
35
- end
36
-
37
- def [](*args) # TODO: remove in 1.2.
38
- warn "[Trailblazer] Operation[] is deprecated. Please use Operation.() and have a nice day."
39
- call(*args)
40
- end
41
-
42
- # Runs #setup! but doesn't process the operation.
43
- def present(params)
44
- build_operation(params)
45
- end
46
-
47
- # This is a DSL method. Use ::contract_class and ::contract_class= for the explicit version.
48
- # Op.contract #=> returns contract class
49
- # Op.contract do .. end # defines contract
50
- # Op.contract CommentForm # copies (and subclasses) external contract.
51
- # Op.contract CommentForm do .. end # copies and extends contract.
52
- def contract(constant=nil, &block)
53
- return contract_class unless constant or block_given?
54
-
55
- self.contract_class= Class.new(constant) if constant
56
- contract_class.class_eval(&block) if block_given?
57
- end
58
- end
59
-
60
-
61
- def initialize(params, options={})
62
- @options = options
63
- @valid = true
64
-
65
- setup!(params) # assign/find the model
66
- end
67
-
68
- # Operation.run(body: "Fabulous!") #=> [true, <Comment body: "Fabulous!">]
69
- def run
70
- process(@params)
71
-
72
- [valid?, self]
73
- end
74
-
75
- attr_reader :model
76
-
77
- def errors
78
- contract.errors
79
- end
80
-
81
- def valid?
82
- @valid
83
- end
84
-
85
- def contract(*args)
86
- contract!(*args)
87
- end
88
-
89
- private
90
- module Setup
91
- attr_writer :model
92
-
93
- def setup!(params)
94
- params = assign_params!(params)
95
- setup_params!(params)
96
- build_model!(params)
97
- params # TODO: test me.
98
- end
99
-
100
- def assign_params!(params)
101
- @params = params!(params)
102
- end
103
-
104
- # Overwrite #params! if you need to change its structure, by returning a new params object
105
- # from this method.
106
- # This is helpful if you don't want to change the original via #setup_params!.
107
- def params!(params)
108
- params
109
- end
110
-
111
- def setup_params!(params)
112
- end
113
-
114
- def build_model!(*args)
115
- assign_model!(*args) # @model = ..
116
- setup_model!(*args)
117
- end
118
-
119
- def assign_model!(*args)
120
- @model = model!(*args)
121
- end
122
-
123
- # Implement #model! to find/create your operation model (if required).
124
- def model!(params)
125
- end
126
-
127
- # Override to add attributes that can be infered from params.
128
- def setup_model!(params)
129
- end
130
- end
131
- include Setup
132
-
133
- def validate(params, model=nil, contract_class=nil)
134
- contract!(model, contract_class)
135
-
136
- if @valid = validate_contract(params)
137
- yield contract if block_given?
138
- else
139
- raise!(contract)
140
- end
141
-
142
- @valid
143
- end
144
-
145
- def validate_contract(params)
146
- contract.validate(params)
147
- end
148
-
149
- def invalid!(result=self)
150
- @valid = false
151
- result
152
- end
153
-
154
- # When using Op.(), an invalid contract will raise an exception.
155
- def raise!(contract)
156
- raise InvalidContract.new(contract.errors.to_s) if @options[:raise_on_invalid]
157
- end
158
-
159
- # Instantiate the contract, either by using the user's contract passed into #validate
160
- # or infer the Operation contract.
161
- def contract_for(model=nil, contract_class=nil)
162
- model ||= self.model
163
- contract_class ||= self.class.contract_class
164
-
165
- contract_class.new(model)
166
- end
167
-
168
- def contract!(*args)
169
- @contract ||= contract_for(*args)
170
- end
171
-
172
- class InvalidContract < RuntimeError
173
- end
174
- end
175
- end