trailblazer 1.1.1 → 2.1.0

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 (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