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