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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ac7e7ac2057c06dfd75019723fda75712ac56a8
4
- data.tar.gz: c1b64e6256e2306a55d0ea5eb40ff67a84b8aa14
3
+ metadata.gz: cdbadcacf508d04b2311ff907f4e0335d95c1f97
4
+ data.tar.gz: cdea00d215f50347970f4faad21f661e245b895f
5
5
  SHA512:
6
- metadata.gz: 7dc34775e9e4e2e90d996c2df0acd5fbe5d83e2ba85aec6cf265d4756a445f84d9f3528d1faefbae392213b4a9987e393f72895eba1b533152b35fced3a1d8b4
7
- data.tar.gz: 01744c20155bf707665259b621ff42f980cc1bd4efbea2d4d55e419a9179f8d9402f4ddfc86aef36fea5c6fd7e8d26bacedec1194c34936f399c442297562bc3
6
+ metadata.gz: d8f7c14fc056a4e3feb0a15338f2104cc5aa06129736b040bb17f22450a438b06a87487e56d1afc2f451653f2701104d11452ca1958c2181647cdb3ac46f11ab
7
+ data.tar.gz: 21f69b585962e0781974986ecb0f7c8acf7e00f4fcbd0c04d4792fd1d73ddf55e13331205d933adbd6f46011f53f853b37f818e5b994969f7b47c8198096bf13
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.0
4
- - 2.1.2
5
- - 2.0.0
3
+ - 2.2
4
+ - 2.1
5
+ - 2.0
6
6
  gemfiles:
7
7
  - gemfiles/Gemfile.reform-2.0
8
8
  - gemfiles/Gemfile.reform-2.1
9
9
  before_install:
10
- - gem install bundler
10
+ - gem install bundler
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 "reform", path: "../reform"
8
- gem "disposable", path: "../disposable"
7
+ # gem "disposable", path: "../disposable"
9
8
  gem "virtus"
10
- # gem "reform", github: "apotonick/reform"
11
- # gem "roar", github: "apotonick/roar"
12
- # gem "reform", "~> 2.0.5"
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, Lotus, or Roda, it wouldn't know. This makes them an ideal replacement for test factories.
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
 
@@ -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 Lotus, Roda, Rails, etc.
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
@@ -88,6 +88,8 @@ module Trailblazer
88
88
 
89
89
  private
90
90
  module Setup
91
+ attr_writer :model
92
+
91
93
  def setup!(params)
92
94
  params = assign_params!(params)
93
95
  setup_params!(params)
@@ -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 = Guard::Permission.new { true } # return true per default.
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).tap do |policy|
61
- policy.send(@action) || yield(policy, @action) if block_given?
62
- end
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
- # Adds #evaluate_policy to Operation#setup!
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
- # Use Guard::Permission.
13
- def permission_class
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
- self.class.policy_config.(self, params) or raise NotAuthorizedError.new
22
+ call_policy(params) or raise policy_exception
20
23
  end
21
24
 
22
- # Encapsulates the operation's policy which is usually called in Op#setup!.
23
- class Permission
24
- def initialize(*args, &block)
25
- @callable, @args = Uber::Options::Value.new(block), args
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
- def call(context, *args)
29
- @callable.(context, *args)
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.inheritable_attr :_representer_class
11
- base.extend ClassMethods
10
+ base.extend DSL
12
11
  end
13
12
 
14
- module ClassMethods
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
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -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
@@ -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
@@ -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
@@ -4,4 +4,6 @@ require 'minitest/autorun'
4
4
  require "reform/form/active_model/validations"
5
5
  Reform::Form.class_eval do
6
6
  include Reform::Form::ActiveModel::Validations
7
- end
7
+ end
8
+
9
+ require "trailblazer/operation/model"
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.0
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: 2015-12-22 00:00:00.000000000 Z
11
+ date: 2016-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber