trailblazer-compat 0.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/README.md +76 -0
- data/Rakefile +10 -0
- data/lib/trailblazer/1.1/autoloading.rb +15 -0
- data/lib/trailblazer/1.1/endpoint.rb +31 -0
- data/lib/trailblazer/1.1/operation.rb +175 -0
- data/lib/trailblazer/1.1/operation/builder.rb +26 -0
- data/lib/trailblazer/1.1/operation/callback.rb +53 -0
- data/lib/trailblazer/1.1/operation/collection.rb +6 -0
- data/lib/trailblazer/1.1/operation/controller.rb +87 -0
- data/lib/trailblazer/1.1/operation/controller/active_record.rb +21 -0
- data/lib/trailblazer/1.1/operation/dispatch.rb +3 -0
- data/lib/trailblazer/1.1/operation/model.rb +50 -0
- data/lib/trailblazer/1.1/operation/model/active_model.rb +11 -0
- data/lib/trailblazer/1.1/operation/model/dsl.rb +29 -0
- data/lib/trailblazer/1.1/operation/model/external.rb +34 -0
- data/lib/trailblazer/1.1/operation/module.rb +29 -0
- data/lib/trailblazer/1.1/operation/policy.rb +85 -0
- data/lib/trailblazer/1.1/operation/policy/guard.rb +35 -0
- data/lib/trailblazer/1.1/operation/representer.rb +98 -0
- data/lib/trailblazer/1.1/operation/resolver.rb +30 -0
- data/lib/trailblazer/1.1/operation/responder.rb +18 -0
- data/lib/trailblazer/1.1/operation/uploaded_file.rb +77 -0
- data/lib/trailblazer/1.1/operation/worker.rb +112 -0
- data/lib/trailblazer/1.1/rails.rb +21 -0
- data/lib/trailblazer/1.1/rails/autoloading.rb +3 -0
- data/lib/trailblazer/1.1/rails/railtie.rb +24 -0
- data/lib/trailblazer/1.1/rails/test/integration.rb +6 -0
- data/lib/trailblazer/compat.rb +50 -0
- data/lib/trailblazer/compat/version.rb +5 -0
- data/trailblazer-compat.gemspec +25 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2156ecbbb4653bcc48e2fd0dca22528b7079e4f
|
4
|
+
data.tar.gz: 8558904f18e93de025483229552bd0ddc4f8134e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e41a477d030046014dce30b2aecef41efa6c5fa6bafa879e3b9a5037ed12d88ecfb53112f0840650f35b358bc6b7db112acfc7b84a9600d664dbe866906d35f1
|
7
|
+
data.tar.gz: 10bf310b9094eb11a0d0a431690afe9b528e145489cfacf7927e1a6d822aaa25c1b3a78501435fcc7f9eda3e31963eb095b060719a03d59df431bd2ce110e635
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Trailblazer::Compat
|
2
|
+
|
3
|
+
This gem provides a seamless-er™ upgrade from TRB 1.1 to 2.x.
|
4
|
+
|
5
|
+
It allows to run both old TRB 1.1 operations along with new or refactored 2.x code in the same application, making it easier to upgrade operation code `step`-wise (no pun intended!) or add new TRB2 operations, workflows, etc. without having to change the old code.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Your exisiting application's `Gemfile` should point to the new `trailblazer` gem.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "trailblazer", ">= 2.0.0"
|
13
|
+
gem "trailblazer-compat"
|
14
|
+
```
|
15
|
+
|
16
|
+
In a Rails application, you also need to pull the 1.x line of the `trailblazer-rails` gem.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "trailblazer-rails", ">= 1.0.0"
|
20
|
+
```
|
21
|
+
|
22
|
+
## Upgrade Path / Overview
|
23
|
+
|
24
|
+
The complete migration path is [documented here](http://trailblazer.to/gems/trailblazer/upgrading-1-to-2.html).
|
25
|
+
|
26
|
+
1. You can keep old TRB1 operations.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# /app/concepts/song/create.rb
|
30
|
+
class Song
|
31
|
+
class Create < Trailblazer::Operation
|
32
|
+
model Song, :create
|
33
|
+
policy Song::Policy, :admin?
|
34
|
+
|
35
|
+
contract do
|
36
|
+
property :id
|
37
|
+
# ...
|
38
|
+
end
|
39
|
+
|
40
|
+
def process(params)
|
41
|
+
validate(params[:song]) do |form|
|
42
|
+
form.save
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
2. At any point, you can introduce new TRB2 operations or update old classes by inheriting from `Trailblazer::Operation.version(2)`.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# /app/concepts/song/create.rb
|
52
|
+
class Song
|
53
|
+
class Create < Trailblazer::Operation.version(2)
|
54
|
+
class Form < Reform::Form
|
55
|
+
property :id
|
56
|
+
# ...
|
57
|
+
end
|
58
|
+
|
59
|
+
class New < Trailblazer::Operation.version(2)
|
60
|
+
step Model( Song, :new )
|
61
|
+
step Policy::Pundit( Song::Policy, :admin? )
|
62
|
+
step Contract::Build( constant: Form )
|
63
|
+
end
|
64
|
+
|
65
|
+
step Nested(New)
|
66
|
+
step Contract::Validate( key: :admin )
|
67
|
+
step Contract::Persist()
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
3. Should you ever be finished updating your application, simply remove the `trailblazer-compat` gem from the `Gemfile`. You can then safely delete `.version(2)` across all files.
|
73
|
+
|
74
|
+
## Development Status
|
75
|
+
|
76
|
+
The `compat` gem tries to make the transition to newer versions as painless as possible. However, if you run into any problems specific to your application, please [don't hesitate to contact us](https://gitter.im/trailblazer/chat). Pull requests (even ugly hacks) are appreciated in this gem, and this gem only.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Trailblazer::V1_1.class_eval do
|
2
|
+
autoload :NotAuthorizedError, "trailblazer/1.1/operation/policy"
|
3
|
+
end
|
4
|
+
|
5
|
+
Trailblazer::V1_1::Operation.class_eval do
|
6
|
+
autoload :Controller, "trailblazer/1.1/operation/controller"
|
7
|
+
autoload :Model, "trailblazer/1.1/operation/model"
|
8
|
+
autoload :Collection, "trailblazer/1.1/operation/collection"
|
9
|
+
autoload :Dispatch, "trailblazer/1.1/operation/dispatch" # TODO: remove in 1.2.
|
10
|
+
autoload :Callback, "trailblazer/1.1/operation/callback"
|
11
|
+
autoload :Module, "trailblazer/1.1/operation/module"
|
12
|
+
autoload :Representer,"trailblazer/1.1/operation/representer"
|
13
|
+
autoload :Policy, "trailblazer/1.1/operation/policy"
|
14
|
+
autoload :Resolver, "trailblazer/1.1/operation/resolver"
|
15
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Trailblazer::V1_1
|
2
|
+
# Encapsulates HTTP-specific logic needed before running an operation.
|
3
|
+
# Right now, all this does is #document_body! which figures out whether or not to pass the request body
|
4
|
+
# into params, so the operation can use a representer to deserialize the original document.
|
5
|
+
# To be used in Hanami, Roda, Rails, etc.
|
6
|
+
class Endpoint
|
7
|
+
def initialize(operation_class, params, request, options)
|
8
|
+
@operation_class = operation_class
|
9
|
+
@params = params
|
10
|
+
@request = request
|
11
|
+
@is_document = options[:is_document]
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
document_body! if @is_document
|
16
|
+
yield @params# Create.run(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
attr_reader :params, :operation_class, :request
|
21
|
+
|
22
|
+
def document_body!
|
23
|
+
# this is what happens:
|
24
|
+
# respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
|
25
|
+
concept_name = operation_class.model_class.to_s.underscore # this could be renamed to ::concept_class soon.
|
26
|
+
request_body = request.body.respond_to?(:string) ? request.body.string : request.body.read
|
27
|
+
|
28
|
+
params.merge!(concept_name => request_body)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require "reform"
|
2
|
+
|
3
|
+
module Trailblazer::V1_1
|
4
|
+
class Operation
|
5
|
+
require "trailblazer/1.1/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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "uber/builder"
|
2
|
+
|
3
|
+
# Allows to add builders via ::builds.
|
4
|
+
module Trailblazer::V1_1::Operation::Builder
|
5
|
+
def self.extended(extender)
|
6
|
+
extender.send(:include, Uber::Builder)
|
7
|
+
end
|
8
|
+
|
9
|
+
def builder_class
|
10
|
+
@builders
|
11
|
+
end
|
12
|
+
|
13
|
+
def builder_class=(constant)
|
14
|
+
@builders = constant
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
# Runs the builders for this operation class to figure out the actual class.
|
19
|
+
def build_operation_class(*args)
|
20
|
+
class_builder(self).(*args) # Uber::Builder::class_builder(context)
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_operation(params, options={})
|
24
|
+
build_operation_class(params).new(params, options)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "declarative"
|
2
|
+
require "disposable/callback"
|
3
|
+
|
4
|
+
module Trailblazer::V1_1::Operation::Callback
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.extend Declarative::Heritage::Inherited
|
9
|
+
base.extend Declarative::Heritage::DSL
|
10
|
+
end
|
11
|
+
|
12
|
+
def callback!(name=:default, options={ operation: self, contract: contract, params: @params }) # FIXME: test options.
|
13
|
+
config = self.class.callbacks.fetch(name) # TODO: test exception
|
14
|
+
group = config[:group].new(contract)
|
15
|
+
|
16
|
+
options[:context] ||= (config[:context] == :operation ? self : group)
|
17
|
+
group.(options)
|
18
|
+
|
19
|
+
invocations[name] = group
|
20
|
+
end
|
21
|
+
|
22
|
+
def dispatch!(*args, &block)
|
23
|
+
callback!(*args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def invocations
|
27
|
+
@invocations ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def callbacks
|
32
|
+
@callbacks ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def callback(name=:default, constant=nil, &block)
|
36
|
+
return callbacks[name][:group] unless constant or block_given?
|
37
|
+
|
38
|
+
add_callback(name, constant, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def add_callback(name, constant, &block)
|
43
|
+
heritage.record(:add_callback, name, constant, &block)
|
44
|
+
|
45
|
+
callbacks[name] ||= {
|
46
|
+
group: Class.new(constant || Disposable::Callback::Group),
|
47
|
+
context: constant ? nil : :operation # `context: :operation` when the callback is inline. `context: group` otherwise.
|
48
|
+
}
|
49
|
+
|
50
|
+
callbacks[name][:group].class_eval(&block) if block_given?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "trailblazer/1.1/endpoint"
|
2
|
+
|
3
|
+
module Trailblazer::V1_1::Operation::Controller
|
4
|
+
private
|
5
|
+
def form(operation_class, options={})
|
6
|
+
run_v2_for(operation_class, options) and return
|
7
|
+
|
8
|
+
res, op, options = operation!(operation_class, options)
|
9
|
+
op.contract.prepopulate!(options) # equals to @form.prepopulate!
|
10
|
+
|
11
|
+
op.contract
|
12
|
+
end
|
13
|
+
|
14
|
+
# Provides the operation instance, model and contract without running #process.
|
15
|
+
# Returns the operation.
|
16
|
+
def present(operation_class, options={})
|
17
|
+
run_v2_for(operation_class, options) and return
|
18
|
+
|
19
|
+
res, op = operation!(operation_class, options.merge(skip_form: true))
|
20
|
+
op
|
21
|
+
end
|
22
|
+
|
23
|
+
def collection(*args)
|
24
|
+
res, op = operation!(*args)
|
25
|
+
@collection = op.model
|
26
|
+
op
|
27
|
+
end
|
28
|
+
|
29
|
+
def run(operation_class, options={}, &block)
|
30
|
+
run_v2_for(operation_class, options, &block) and return
|
31
|
+
|
32
|
+
res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
|
33
|
+
|
34
|
+
yield op if res and block_given?
|
35
|
+
|
36
|
+
op
|
37
|
+
end
|
38
|
+
|
39
|
+
# The block passed to #respond is always run, regardless of the validity result.
|
40
|
+
def respond(operation_class, options={}, &block)
|
41
|
+
res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
|
42
|
+
namespace = options.delete(:namespace) || []
|
43
|
+
|
44
|
+
return respond_with *namespace, op, options if not block_given?
|
45
|
+
respond_with *namespace, op, options, &Proc.new { |formats| block.call(op, formats) } if block_given?
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def operation!(operation_class, options={}) # or #model or #setup.
|
50
|
+
operation_for!(operation_class, options) { |params| [true, operation_class.present(params)] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_params!(params)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Override and return arbitrary params object.
|
57
|
+
def params!(params)
|
58
|
+
params
|
59
|
+
end
|
60
|
+
|
61
|
+
# Normalizes parameters and invokes the operation (including its builders).
|
62
|
+
def operation_for!(operation_class, options, &block)
|
63
|
+
params = options[:params] || self.params # TODO: test params: parameter properly in all 4 methods.
|
64
|
+
params = params!(params)
|
65
|
+
process_params!(params) # deprecate or rename to #setup_params!
|
66
|
+
|
67
|
+
res, op = Trailblazer::V1_1::Endpoint.new(operation_class, params, request, options).(&block)
|
68
|
+
setup_operation_instance_variables!(op, options)
|
69
|
+
|
70
|
+
[res, op, options.merge(params: params)]
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup_operation_instance_variables!(operation, options)
|
74
|
+
@operation = operation
|
75
|
+
@model = operation.model
|
76
|
+
@form = operation.contract unless options[:skip_form]
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_v2_for(operation_class, *args, &block)
|
80
|
+
if operation_class < Trailblazer::V2::Operation
|
81
|
+
# run_v2(operation_class, *args)
|
82
|
+
run_v2(operation_class, &block)
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|