trailblazer 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://badge.fury.io/rb/trailblazer.svg)](http://badge.fury.io/rb/trailblazer)
|
6
5
|
[![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
|
6
|
+
[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/trailblazer.svg)](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
|