trailblazer 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +17 -0
- data/README.md +11 -7
- data/lib/trailblazer/operation/controller.rb +5 -3
- data/lib/trailblazer/rails/railtie.rb +24 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/operation_test.rb +8 -6
- data/test/rails/controller_test.rb +24 -1
- data/test/rails/fake_app/controllers.rb +4 -2
- data/test/rails/fake_app/song/operations.rb +18 -1
- data/trailblazer.gemspec +2 -0
- metadata +31 -5
- data/lib/trailblazer/crud_autoloading.rb +0 -8
- data/test/rails/integration_test.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 994926094dd0136c1436127773dd4c8786cd3cb3
|
4
|
+
data.tar.gz: ce869dcf9f8c01e2fe10847e3d4b54e460537960
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26367bea09297a1bbcf29506d589826f3e83fca5dc45e4bb6d7146a57f2984c60d324cda43f55a52cd08f3523ed5e76d5b0c56bbddd531a2509ccd4e81117861
|
7
|
+
data.tar.gz: ff56573c794feb3afbbc7bac40026f65f418153e9aaef9cbdb6967a01d37412ba1faa95fb4127bab82efd6ad0f5ef10a2ea359a8f94bd8749dbebd480b64d8a2
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# 0.2.0
|
2
|
+
|
3
|
+
## API Changes
|
4
|
+
|
5
|
+
* `Controller#present` no longer calls `respond_to`, but lets you do the rendering. This will soon be re-introduced using `respond(present: true)`.
|
6
|
+
* `Controller#form` did not respect builders, this is fixed now.
|
7
|
+
* Use `request.body.read` in Unicorn/etc. environments in `Controller#respond`.
|
8
|
+
|
9
|
+
## Stuff
|
10
|
+
|
11
|
+
* Autoloading changed, again. We now `require_dependency` in every request in dev.
|
12
|
+
|
13
|
+
# 0.1.3
|
14
|
+
|
15
|
+
* `crud_autoloading` now simply `require_dependency`s model files, then does the same for the CRUD operation file. This should fix random undefined constant problems in development.
|
16
|
+
* `Controller#form` did not use builders. This is fixed now.
|
17
|
+
|
1
18
|
# 0.1.2
|
2
19
|
|
3
20
|
* Add `crud_autoloading`.
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ In a nutshell: Trailblazer makes you write **logicless models** that purely act
|
|
6
6
|
|
7
7
|
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
8
8
|
|
9
|
-
Please
|
9
|
+
Please buy my book [Trailblazer - A new architecture for Rails](https://leanpub.com/trailblazer) and [let me know](http://twitter.com/apotonick) what you think! I am still working on the book but keep adding new chapters every other week. It will be about 300 pages and we're developing a real, full-blown Rails/Trb application.
|
10
10
|
|
11
11
|
The [demo application](https://github.com/apotonick/gemgem-trbrb) implements what we discuss in the book.
|
12
12
|
|
@@ -173,7 +173,7 @@ Again, this will only run the operation's setup and provide the model in `@model
|
|
173
173
|
|
174
174
|
For document-based APIs and request types that are not HTTP the operation will be advised to render the JSON or XML document using the operation's representer.
|
175
175
|
|
176
|
-
Note that `#present` will
|
176
|
+
Note that `#present` will leave rendering up to you - `respond_to` is _not_ called.
|
177
177
|
|
178
178
|
### Controller API
|
179
179
|
|
@@ -415,18 +415,22 @@ Use our autoloading if you dislike explicit requires.
|
|
415
415
|
You can just add
|
416
416
|
|
417
417
|
```ruby
|
418
|
-
require
|
418
|
+
require "trailblazer/autoloading"
|
419
419
|
```
|
420
420
|
|
421
|
-
to `config/initializers/trailblazer.rb` and implementation
|
421
|
+
to `config/initializers/trailblazer.rb` and implementation classes like `Operation` will be automatically loaded.
|
422
422
|
|
423
|
-
|
423
|
+
## Operation Autoloading
|
424
|
+
|
425
|
+
If you structure your CRUD operations using the `app/concepts/*/crud.rb` file layout we use in the book, the `crud.rb` files are not gonna be found by Rails automatically. It is a good idea to enable CRUD autoloading.
|
426
|
+
|
427
|
+
At the end of your `config/application.rb` file, add the following.
|
424
428
|
|
425
429
|
```ruby
|
426
|
-
require trailblazer/
|
430
|
+
require "trailblazer/rails/railtie"
|
427
431
|
```
|
428
432
|
|
429
|
-
This will go through `app/concepts/`, find all the `crud.rb` files,
|
433
|
+
This will go through `app/concepts/`, find all the `crud.rb` files, autoload their corresponding namespace (e.g. `Thing`, which is a model) and then load the `crud.rb` file.
|
430
434
|
|
431
435
|
|
432
436
|
## Why?
|
@@ -5,7 +5,7 @@ private
|
|
5
5
|
def form(operation_class, params=self.params) # consider private.
|
6
6
|
process_params!(params)
|
7
7
|
|
8
|
-
@operation = operation_class.
|
8
|
+
@operation = operation_class.present(params)
|
9
9
|
@form = @operation.contract
|
10
10
|
@model = @operation.model
|
11
11
|
|
@@ -19,7 +19,8 @@ private
|
|
19
19
|
res, op = operation!(operation_class, params) { [true, operation_class.present(params)] }
|
20
20
|
|
21
21
|
yield op if block_given?
|
22
|
-
respond_with op
|
22
|
+
# respond_with op
|
23
|
+
# TODO: implement respond(present: true)
|
23
24
|
end
|
24
25
|
|
25
26
|
# full-on Op[]
|
@@ -64,8 +65,9 @@ private
|
|
64
65
|
# this is what happens:
|
65
66
|
# respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
|
66
67
|
concept_name = operation_class.model_class.to_s.underscore # this could be renamed to ::concept_class soon.
|
68
|
+
request_body = request.body.respond_to?(:string) ? request.body.string : request.body.read
|
67
69
|
|
68
|
-
params.merge!(concept_name =>
|
70
|
+
params.merge!(concept_name => request_body)
|
69
71
|
end
|
70
72
|
|
71
73
|
res, @operation = yield # Create.run(params)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
def self.autoload_crud_operations(app)
|
4
|
+
Dir.glob("app/concepts/**/crud.rb") do |f|
|
5
|
+
path = f.sub("app/concepts/", "")
|
6
|
+
model = path.sub("/crud.rb", "")
|
7
|
+
|
8
|
+
require_dependency "#{app.root}/app/models/#{model}" # load the model file, first (thing.rb).
|
9
|
+
require_dependency "#{app.root}/#{f}" # load app/concepts/{concept}/crud.rb (Thing::Create, Thing::Update, and so on).
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# thank you, http://stackoverflow.com/a/17573888/465070
|
14
|
+
initializer 'trailblazer.install' do |app|
|
15
|
+
if Rails.configuration.cache_classes
|
16
|
+
Trailblazer::Railtie.autoload_crud_operations(app)
|
17
|
+
else
|
18
|
+
ActionDispatch::Reloader.to_prepare do
|
19
|
+
Trailblazer::Railtie.autoload_crud_operations(app)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/trailblazer/version.rb
CHANGED
data/test/operation_test.rb
CHANGED
@@ -40,6 +40,8 @@ class OperationRunTest < MiniTest::Spec
|
|
40
40
|
|
41
41
|
# return operation when ::call
|
42
42
|
it { Operation.call(true).must_equal operation }
|
43
|
+
it { Operation.(true).must_equal operation }
|
44
|
+
# #[] is alias for .()
|
43
45
|
it { Operation[true].must_equal operation }
|
44
46
|
|
45
47
|
# ::[] raises exception when invalid.
|
@@ -88,7 +90,7 @@ class OperationRunTest < MiniTest::Spec
|
|
88
90
|
|
89
91
|
# Operation#contract returns @contract
|
90
92
|
let (:contract) { Operation::Contract.new }
|
91
|
-
it { Operation
|
93
|
+
it { Operation.(true).contract.must_equal contract }
|
92
94
|
end
|
93
95
|
|
94
96
|
|
@@ -119,7 +121,7 @@ class OperationTest < MiniTest::Spec
|
|
119
121
|
# ::run
|
120
122
|
it { OperationWithoutValidateCall.run(Object).must_equal [true, Object] }
|
121
123
|
# ::[]
|
122
|
-
it { OperationWithoutValidateCall
|
124
|
+
it { OperationWithoutValidateCall.(Object).must_equal(Object) }
|
123
125
|
# ::run with invalid!
|
124
126
|
it { OperationWithoutValidateCall.run(nil).must_equal [false, nil] }
|
125
127
|
# ::run with block, invalid
|
@@ -155,7 +157,7 @@ class OperationTest < MiniTest::Spec
|
|
155
157
|
end
|
156
158
|
|
157
159
|
it { OperationWithValidateBlock.run(false).last.secret_contract.must_equal nil }
|
158
|
-
it('zzz') { OperationWithValidateBlock
|
160
|
+
it('zzz') { OperationWithValidateBlock.(true).secret_contract.must_equal OperationWithValidateBlock.contract_class.new.extend(Comparable) }
|
159
161
|
|
160
162
|
# manually setting @valid
|
161
163
|
class OperationWithManualValid < Trailblazer::Operation
|
@@ -168,7 +170,7 @@ class OperationTest < MiniTest::Spec
|
|
168
170
|
# ::run
|
169
171
|
it { OperationWithManualValid.run(Object).must_equal [false, Object] }
|
170
172
|
# ::[]
|
171
|
-
it { OperationWithManualValid
|
173
|
+
it { OperationWithManualValid.(Object).must_equal(Object) }
|
172
174
|
|
173
175
|
|
174
176
|
# re-assign params
|
@@ -281,8 +283,8 @@ class OperationBuilderTest < MiniTest::Spec
|
|
281
283
|
it { Operation.run({}).last.must_equal "operation" }
|
282
284
|
it { Operation.run({sub: true}).last.must_equal "sub:operation" }
|
283
285
|
|
284
|
-
it { Operation
|
285
|
-
it { Operation
|
286
|
+
it { Operation.({}).must_equal "operation" }
|
287
|
+
it { Operation.({sub: true}).must_equal "sub:operation" }
|
286
288
|
end
|
287
289
|
|
288
290
|
# ::contract builds Reform::Form class
|
@@ -113,13 +113,29 @@ class ResponderRespondWithJSONTest < ActionController::TestCase
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
# TODO: merge with above tests on SongsController.
|
117
|
+
class ControllerRespondTest < ActionController::TestCase
|
118
|
+
tests BandsController
|
119
|
+
|
120
|
+
test "#respond with builds" do
|
121
|
+
post :create, band: {name: "SNFU"}, admin: true
|
122
|
+
assert_response 302
|
123
|
+
assert_equal "SNFU [ADMIN]", Band.last.name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
116
127
|
|
117
128
|
class ResponderRunTest < ActionController::TestCase
|
118
129
|
tests BandsController
|
119
130
|
|
120
131
|
test "[html/valid]" do
|
121
132
|
put :update, {id: 1, band: {name: "Nofx"}}
|
122
|
-
assert_equal "no block: Nofx, Essen", response.body
|
133
|
+
assert_equal "no block: Nofx, Essen, Band::Create", response.body
|
134
|
+
end
|
135
|
+
|
136
|
+
test "[html/valid] with builds" do
|
137
|
+
put :update, {id: 1, band: {name: "Nofx"}, admin: true}
|
138
|
+
assert_equal "no block: Nofx [ADMIN], Essen, Band::Create::Admin", response.body
|
123
139
|
end
|
124
140
|
|
125
141
|
test "with block [html/valid]" do
|
@@ -146,6 +162,7 @@ class ControllerPresentTest < ActionController::TestCase
|
|
146
162
|
assert_equal "bands/show.html: Band,Band,true,Band::Update,Essen\n", response.body
|
147
163
|
end
|
148
164
|
|
165
|
+
# TODO: this implicitely tests builds. maybe have separate test for that?
|
149
166
|
test "#present [JSON]" do
|
150
167
|
band = Band::Create[band: {name: "Nofx"}].model
|
151
168
|
|
@@ -171,4 +188,10 @@ class ControllerFormTest < ActionController::TestCase
|
|
171
188
|
|
172
189
|
assert_select "b", "Band,Band,true,Band::Create,Essen"
|
173
190
|
end
|
191
|
+
|
192
|
+
test "#form with builder" do
|
193
|
+
get :new, admin: true
|
194
|
+
|
195
|
+
assert_select "b", ",Band,true,Band::Create::Admin"
|
196
|
+
end
|
174
197
|
end
|
@@ -49,7 +49,9 @@ class BandsController < ApplicationController
|
|
49
49
|
present Band::Update do |op|
|
50
50
|
@klass = op.model.class
|
51
51
|
@locality = params[:band][:locality] unless params[:format] == "json"
|
52
|
-
|
52
|
+
|
53
|
+
render json: op.to_json if params[:format] == "json"
|
54
|
+
end # render :show
|
53
55
|
end
|
54
56
|
|
55
57
|
def new
|
@@ -82,7 +84,7 @@ ERB
|
|
82
84
|
def update
|
83
85
|
run Band::Create
|
84
86
|
|
85
|
-
render text: "no block: #{@operation.model.name}, #{params[:band][:locality]}"
|
87
|
+
render text: "no block: #{@operation.model.name}, #{params[:band][:locality]}, #{@operation.class}"
|
86
88
|
end
|
87
89
|
|
88
90
|
def update_with_block
|
@@ -66,8 +66,25 @@ class Band < ActiveRecord::Base
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
class Admin < self
|
70
|
+
def process(params)
|
71
|
+
res = super
|
72
|
+
model.update_attribute :name, "#{model.name} [ADMIN]"
|
73
|
+
res
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# TODO: wait for uber 0.0.10 and @dutow.
|
78
|
+
# builds -> (params)
|
79
|
+
# return JSON if params[:format] == "json"
|
80
|
+
# return Admin if params[:admin]
|
81
|
+
# end
|
69
82
|
builds do |params|
|
70
|
-
|
83
|
+
if params[:format] == "json"
|
84
|
+
JSON
|
85
|
+
elsif params[:admin]
|
86
|
+
Admin
|
87
|
+
end
|
71
88
|
end
|
72
89
|
end
|
73
90
|
|
data/trailblazer.gemspec
CHANGED
@@ -28,4 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "minitest"
|
29
29
|
# spec.add_development_dependency "minitest-spec-rails" # TODO: can anyone make this work (test/rails).
|
30
30
|
spec.add_development_dependency "sidekiq", "~> 3.1.0"
|
31
|
+
spec.add_development_dependency "rails"
|
32
|
+
spec.add_development_dependency "sqlite3"
|
31
33
|
end
|
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: 0.
|
4
|
+
version: 0.2.0
|
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: 2015-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -128,6 +128,34 @@ dependencies:
|
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: 3.1.0
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: rails
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: sqlite3
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
131
159
|
description: Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation,
|
132
160
|
an intuitive code structure and gives you an object-oriented architecture.
|
133
161
|
email:
|
@@ -151,7 +179,6 @@ files:
|
|
151
179
|
- gemfiles/Gemfile.rails.lock
|
152
180
|
- lib/trailblazer.rb
|
153
181
|
- lib/trailblazer/autoloading.rb
|
154
|
-
- lib/trailblazer/crud_autoloading.rb
|
155
182
|
- lib/trailblazer/operation.rb
|
156
183
|
- lib/trailblazer/operation/controller.rb
|
157
184
|
- lib/trailblazer/operation/crud.rb
|
@@ -159,6 +186,7 @@ files:
|
|
159
186
|
- lib/trailblazer/operation/responder.rb
|
160
187
|
- lib/trailblazer/operation/uploaded_file.rb
|
161
188
|
- lib/trailblazer/operation/worker.rb
|
189
|
+
- lib/trailblazer/rails/railtie.rb
|
162
190
|
- lib/trailblazer/version.rb
|
163
191
|
- test/crud_test.rb
|
164
192
|
- test/fixtures/apotomo.png
|
@@ -174,7 +202,6 @@ files:
|
|
174
202
|
- test/rails/fake_app/song/operations.rb
|
175
203
|
- test/rails/fake_app/views/bands/show.html.erb
|
176
204
|
- test/rails/fake_app/views/songs/new.html.erb
|
177
|
-
- test/rails/integration_test.rb
|
178
205
|
- test/rails/test_helper.rb
|
179
206
|
- test/responder_test.rb
|
180
207
|
- test/test_helper.rb
|
@@ -220,7 +247,6 @@ test_files:
|
|
220
247
|
- test/rails/fake_app/song/operations.rb
|
221
248
|
- test/rails/fake_app/views/bands/show.html.erb
|
222
249
|
- test/rails/fake_app/views/songs/new.html.erb
|
223
|
-
- test/rails/integration_test.rb
|
224
250
|
- test/rails/test_helper.rb
|
225
251
|
- test/responder_test.rb
|
226
252
|
- test/test_helper.rb
|
@@ -1,8 +0,0 @@
|
|
1
|
-
# FIXME: this will only work for operations embedded in a MODEL namespace.
|
2
|
-
# this is really hacky and assumes a lot about your structure, but it works for now. don't include it if you don't like it.
|
3
|
-
Dir.glob("app/concepts/**/crud.rb") do |f|
|
4
|
-
path = f.sub("app/concepts/", "")
|
5
|
-
|
6
|
-
path.sub("/crud.rb", "").camelize.constantize # load the model first (Thing).
|
7
|
-
require_dependency path # load model/crud.rb (Thing::Create, Thing::Update, and so on).
|
8
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class IntegrationTest < ActionController::TestCase
|
4
|
-
tests BandsController
|
5
|
-
|
6
|
-
# test rendering JSON of populated band.
|
7
|
-
test "#present [JSON]" do
|
8
|
-
band = Band::Create[band: {name: "Nofx", songs: [{title: "Murder The Government"}, {title: "Eat The Meek"}]}].model
|
9
|
-
|
10
|
-
get :show, id: band.id, format: :json
|
11
|
-
assert_equal "{\"name\":\"Nofx\",\"songs\":[{\"title\":\"Murder The Government\"},{\"title\":\"Eat The Meek\"}]}", response.body
|
12
|
-
end
|
13
|
-
|
14
|
-
# parsing incoming complex document.
|
15
|
-
test "create [JSON]" do
|
16
|
-
post :create, "{\"name\":\"Nofx\",\"songs\":[{\"title\":\"Murder The Government\"},{\"title\":\"Eat The Meek\"}]}", format: :json
|
17
|
-
assert_response 201
|
18
|
-
|
19
|
-
band = Band.last
|
20
|
-
assert_equal "Nofx", band.name
|
21
|
-
assert_equal "Murder The Government", band.songs[0].title
|
22
|
-
end
|
23
|
-
end
|