trailblazer 1.1.0 → 1.1.1
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 +4 -4
- data/.travis.yml +4 -4
- data/CHANGES.md +7 -1
- data/Gemfile +6 -10
- data/README.md +13 -2
- data/lib/trailblazer/endpoint.rb +2 -2
- data/lib/trailblazer/operation.rb +2 -0
- data/lib/trailblazer/operation/policy.rb +5 -4
- data/lib/trailblazer/operation/policy/guard.rb +16 -15
- data/lib/trailblazer/operation/representer.rb +7 -4
- data/lib/trailblazer/version.rb +1 -1
- data/test/operation/guard_test.rb +36 -0
- data/test/operation_test.rb +11 -1
- data/test/representer_test.rb +61 -1
- data/test/test_helper.rb +3 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cdbadcacf508d04b2311ff907f4e0335d95c1f97
|
4
|
+
data.tar.gz: cdea00d215f50347970f4faad21f661e245b895f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8f7c14fc056a4e3feb0a15338f2104cc5aa06129736b040bb17f22450a438b06a87487e56d1afc2f451653f2701104d11452ca1958c2181647cdb3ac46f11ab
|
7
|
+
data.tar.gz: 21f69b585962e0781974986ecb0f7c8acf7e00f4fcbd0c04d4792fd1d73ddf55e13331205d933adbd6f46011f53f853b37f818e5b994969f7b47c8198096bf13
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 1.1.1
|
2
|
+
|
3
|
+
* Rename `Operation::Representer::ClassMethods` to `Operation::Representer::DSL` and allow to use `DSL` and `Rendering` without `Deserialization` so you can use two different representers.
|
4
|
+
* `Policy::Guard::policy` now also accepts a `Callable` object.
|
5
|
+
* Add `Operation#model=`.
|
6
|
+
|
1
7
|
# 1.1.0
|
2
8
|
|
3
9
|
* `Representer#represented` defaults to `model` now, not to `contract` anymore.
|
@@ -129,4 +135,4 @@
|
|
129
135
|
|
130
136
|
# 0.1.0
|
131
137
|
|
132
|
-
* First stable release after almost 6 months of blood, sweat and tears. I know, this is a ridiculously brief codebase but it was a hell of a job to structure everything the way it is now. Enjoy!
|
138
|
+
* First stable release after almost 6 months of blood, sweat and tears. I know, this is a ridiculously brief codebase but it was a hell of a job to structure everything the way it is now. Enjoy!
|
data/Gemfile
CHANGED
@@ -4,15 +4,11 @@ source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
# gem "representable", path: "../representable"
|
7
|
-
gem "
|
8
|
-
gem "disposable", path: "../disposable"
|
7
|
+
# gem "disposable", path: "../disposable"
|
9
8
|
gem "virtus"
|
10
|
-
|
11
|
-
|
12
|
-
# gem "reform", "~> 2.0.
|
13
|
-
# gem "roar"
|
9
|
+
gem "reform", github: "apotonick/reform"
|
10
|
+
gem "roar", github: "apotonick/roar"
|
11
|
+
# gem "reform", "~> 2.0.0"
|
14
12
|
# gem "reform", path: "../reform"
|
15
|
-
gem "roar", path: "../roar"
|
16
|
-
gem "multi_json"
|
17
|
-
|
18
|
-
gem "minitest-line"
|
13
|
+
# gem "roar", path: "../roar"
|
14
|
+
gem "multi_json"
|
data/README.md
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
_Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture._
|
4
4
|
|
5
|
-
[](http://badge.fury.io/rb/trailblazer)
|
6
5
|
[](https://gitter.im/trailblazer/chat)
|
6
|
+
[](http://trailblazer.to/newsletter/)
|
7
|
+
[](http://badge.fury.io/rb/trailblazer)
|
7
8
|
|
8
9
|
|
9
10
|
## Trailblazer In A Nutshell
|
@@ -30,6 +31,16 @@ Again, you can pick which layers you want. Trailblazer doesn't impose technical
|
|
30
31
|
|
31
32
|
Trailblazer is no "complex web of objects and indirection". It solves many problems that have been around for years with a cleanly layered architecture. Only use what you like. And that's the bottom line.
|
32
33
|
|
34
|
+
## Trailblazer Likes 'Em All
|
35
|
+
|
36
|
+
Since Trailblazer decouples the High-Level Stack from the framework, it runs with virtually any Ruby framework. We are constantly working on documenting how to do that.
|
37
|
+
|
38
|
+
* Trailblazer with Rails [Book](http://trailblazer.to/books/trailblazer.html) | [Repository](https://github.com/apotonick/gemgem-trbrb)
|
39
|
+
* Trailblazer with Sinatra [Guide](http://trailblazer.to/guides/sinatra/getting-started.html) | [Repository](https://github.com/apotonick/gemgem-sinatra)
|
40
|
+
* Trailblazer with Hanami - coming soon!
|
41
|
+
* Trailblazer with Roda - coming soon!
|
42
|
+
* Trailblazer with Grape - coming _very_ soon!
|
43
|
+
|
33
44
|
|
34
45
|
## A Concept-Driven OOP Framework
|
35
46
|
|
@@ -101,7 +112,7 @@ Again, the controller only dispatchs to the operation and handles successful/inv
|
|
101
112
|
|
102
113
|
Operations encapsulate business logic and are the heart of a Trailblazer architecture.
|
103
114
|
|
104
|
-
Operations don't know about HTTP or the environment. You could use an operation in Rails,
|
115
|
+
Operations don't know about HTTP or the environment. You could use an operation in Rails, Hanami, or Roda, it wouldn't know. This makes them an ideal replacement for test factories.
|
105
116
|
|
106
117
|
An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form object, models and your business code.
|
107
118
|
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -2,7 +2,7 @@ module Trailblazer
|
|
2
2
|
# Encapsulates HTTP-specific logic needed before running an operation.
|
3
3
|
# Right now, all this does is #document_body! which figures out whether or not to pass the request body
|
4
4
|
# into params, so the operation can use a representer to deserialize the original document.
|
5
|
-
# To be used in
|
5
|
+
# To be used in Hanami, Roda, Rails, etc.
|
6
6
|
class Endpoint
|
7
7
|
def initialize(operation_class, params, request, options)
|
8
8
|
@operation_class = operation_class
|
@@ -28,4 +28,4 @@ module Trailblazer
|
|
28
28
|
params.merge!(concept_name => request_body)
|
29
29
|
end
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
@@ -13,7 +13,7 @@ module Trailblazer
|
|
13
13
|
module DSL
|
14
14
|
def self.extended(extender)
|
15
15
|
extender.inheritable_attr :policy_config
|
16
|
-
extender.policy_config =
|
16
|
+
extender.policy_config = lambda { |*| true } # return true per default.
|
17
17
|
end
|
18
18
|
|
19
19
|
def policy(*args, &block)
|
@@ -57,9 +57,10 @@ module Trailblazer
|
|
57
57
|
# Without a block, return the policy object (which is usually a Pundit-style class).
|
58
58
|
# When block is passed evaluate the default rule and run block when false.
|
59
59
|
def call(user, model, external_policy=nil)
|
60
|
-
build_policy(user, model, external_policy)
|
61
|
-
|
62
|
-
|
60
|
+
policy = build_policy(user, model, external_policy)
|
61
|
+
|
62
|
+
policy.send(@action) || yield(policy, @action) if block_given?
|
63
|
+
policy
|
63
64
|
end
|
64
65
|
|
65
66
|
private
|
@@ -1,34 +1,35 @@
|
|
1
1
|
module Trailblazer
|
2
|
-
#
|
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
|
3
7
|
module Operation::Policy
|
4
8
|
module Guard
|
5
9
|
def self.included(includer)
|
6
|
-
includer.extend(DSL)
|
10
|
+
includer.extend(DSL) # Provides ::policy(CallableObject)
|
7
11
|
includer.extend(ClassMethods)
|
8
12
|
includer.send(:include, Setup)
|
9
13
|
end
|
10
14
|
|
11
15
|
module ClassMethods
|
12
|
-
|
13
|
-
|
14
|
-
Permission
|
16
|
+
def policy(callable=nil, &block)
|
17
|
+
self.policy_config = Uber::Options::Value.new(callable || block)
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
21
|
def evaluate_policy(params)
|
19
|
-
|
22
|
+
call_policy(params) or raise policy_exception
|
20
23
|
end
|
21
24
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
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
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
30
|
+
def policy_exception
|
31
|
+
NotAuthorizedError.new
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
34
|
-
end
|
35
|
+
end
|
@@ -7,11 +7,14 @@
|
|
7
7
|
# TODO: so far, we only support JSON, but it's two lines to change to support any kind of format.
|
8
8
|
module Trailblazer::Operation::Representer
|
9
9
|
def self.included(base)
|
10
|
-
base.
|
11
|
-
base.extend ClassMethods
|
10
|
+
base.extend DSL
|
12
11
|
end
|
13
12
|
|
14
|
-
module
|
13
|
+
module DSL
|
14
|
+
def self.extended(extender)
|
15
|
+
extender.inheritable_attr :_representer_class
|
16
|
+
end
|
17
|
+
|
15
18
|
def representer(constant=nil, &block)
|
16
19
|
return representer_class unless constant or block_given?
|
17
20
|
|
@@ -92,4 +95,4 @@ private
|
|
92
95
|
end
|
93
96
|
end
|
94
97
|
include Deserializer::JSON
|
95
|
-
end
|
98
|
+
end
|
data/lib/trailblazer/version.rb
CHANGED
@@ -80,6 +80,42 @@ class OpPolicyGuardTest < MiniTest::Spec
|
|
80
80
|
it { Index.("true").wont_equal nil }
|
81
81
|
it { assert_raises(Trailblazer::NotAuthorizedError) { Index.(false).wont_equal nil } }
|
82
82
|
end
|
83
|
+
|
84
|
+
describe "with Callable" do
|
85
|
+
class Find < Trailblazer::Operation
|
86
|
+
include Policy::Guard
|
87
|
+
|
88
|
+
class Guardian
|
89
|
+
include Uber::Callable
|
90
|
+
|
91
|
+
def call(context, params)
|
92
|
+
params == "true"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
policy Guardian.new
|
97
|
+
|
98
|
+
def process(*)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it { Find.("true").wont_equal nil }
|
103
|
+
it { assert_raises(Trailblazer::NotAuthorizedError) { Find.(false).wont_equal nil } }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "with Proc" do
|
107
|
+
class Follow < Trailblazer::Operation
|
108
|
+
include Policy::Guard
|
109
|
+
|
110
|
+
policy ->(params) { params == "true" } # TODO: is this really executed in op context?
|
111
|
+
|
112
|
+
def process(*)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it { Follow.("true").wont_equal nil }
|
117
|
+
it { assert_raises(Trailblazer::NotAuthorizedError) { Follow.(false).wont_equal nil } }
|
118
|
+
end
|
83
119
|
end
|
84
120
|
|
85
121
|
class OpBuilderDenyTest < MiniTest::Spec
|
data/test/operation_test.rb
CHANGED
@@ -54,6 +54,16 @@ class OperationModelTest < MiniTest::Spec
|
|
54
54
|
it { Operation.(Object).model.must_equal Object }
|
55
55
|
end
|
56
56
|
|
57
|
+
# Operation#model=.
|
58
|
+
class OperationModelWriterTest < MiniTest::Spec
|
59
|
+
class Operation < Trailblazer::Operation
|
60
|
+
def process(params)
|
61
|
+
self.model = "#{params}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it { Operation.("I can set @model via a private setter").model.to_s.must_equal "I can set @model via a private setter" }
|
66
|
+
end
|
57
67
|
|
58
68
|
class OperationRunTest < MiniTest::Spec
|
59
69
|
class Operation < Trailblazer::Operation
|
@@ -262,4 +272,4 @@ class OperationErrorsTest < MiniTest::Spec
|
|
262
272
|
res, op = Operation.run({})
|
263
273
|
op.errors.to_s.must_equal "{:title=>[\"can't be blank\"]}"
|
264
274
|
end
|
265
|
-
end
|
275
|
+
end
|
data/test/representer_test.rb
CHANGED
@@ -175,4 +175,64 @@ class InternalRepresenterAPITest < MiniTest::Spec
|
|
175
175
|
OptionsShow.present(include: [:id]).to_json.must_equal '{"id":1}'
|
176
176
|
end
|
177
177
|
end
|
178
|
-
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class DifferentParseAndRenderingRepresenterTest < MiniTest::Spec
|
181
|
+
Album = Struct.new(:title)
|
182
|
+
|
183
|
+
# rendering
|
184
|
+
class Create < Trailblazer::Operation
|
185
|
+
extend Representer::DSL
|
186
|
+
include Representer::Rendering # no Deserializer::Hash here or anything.
|
187
|
+
|
188
|
+
contract do
|
189
|
+
property :title
|
190
|
+
end
|
191
|
+
|
192
|
+
representer do
|
193
|
+
property :title, as: :Title
|
194
|
+
end
|
195
|
+
|
196
|
+
def process(params)
|
197
|
+
@model = Album.new
|
198
|
+
validate(params) do
|
199
|
+
contract.sync
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it do
|
205
|
+
Create.(title: "The Kids").to_json.must_equal %{{"Title":"The Kids"}}
|
206
|
+
end
|
207
|
+
|
208
|
+
# parsing
|
209
|
+
class Update < Trailblazer::Operation
|
210
|
+
extend Representer::DSL
|
211
|
+
include Representer::Deserializer::Hash # no Rendering.
|
212
|
+
|
213
|
+
representer do
|
214
|
+
property :title, as: :Title
|
215
|
+
end
|
216
|
+
|
217
|
+
contract do
|
218
|
+
property :title
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
def process(params)
|
223
|
+
@model = Album.new
|
224
|
+
|
225
|
+
validate(params) do
|
226
|
+
contract.sync
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_json(*)
|
231
|
+
%{{"title": "#{model.title}"}}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it do
|
236
|
+
Update.("Title" => "The Kids").to_json.must_equal %{{"title": "The Kids"}}
|
237
|
+
end
|
238
|
+
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|