trailblazer 0.1.2 → 0.2.0
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/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
|

|
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
|