trailblazer 0.1.0 → 0.1.2

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: f0ed3011dfc0a91641be3c62c80a9fdf8f68d9b2
4
- data.tar.gz: 3a219f4bb0f1873088249b39d5a51d097c4ea919
3
+ metadata.gz: 02c7b4eafd8dfcbb9eb29aafd91a9213aa34d0e9
4
+ data.tar.gz: 8720dec06b3af4b127f77ca140c25e71f7a68799
5
5
  SHA512:
6
- metadata.gz: 9d8d68ac22880d537923009618bd71ba6463632e5f9a453e4c1d6d8e03f892ff5ee8cf2d240678549d035acbd5d84c6649c426b10eb311c765d1e713fcc83b4e
7
- data.tar.gz: 556f3942a0e00e5a83a112f88ea1962b0e28a34698acf2e5cbc2b9103c66a14b142a57cf10d8ce623d183858c729d9e9c516c127869d4fe141a78fe7e79aee94
6
+ metadata.gz: 20bd8a8b6b296bea95bd73a27269855af238c03b5617885c6a51015dc40b2ef7d1c5ea7ff394961e8871d0724fd614d1868227afb3112b74685f55f42eb6a9b1
7
+ data.tar.gz: 79c3548b02e02789dc8a1ea55141d1786b710d53dd3213aae0cc0e28a1373c247debae6c1e845522e95235d7befd9fd30968f1ba92fbd7d4fdaaedc5641f9ae1
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.1.2
2
+
3
+ * Add `crud_autoloading`.
4
+
5
+ # 0.1.1
6
+
7
+ * Use reform-1.2.0.
8
+
1
9
  # 0.1.0
2
10
 
3
11
  * 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/README.md CHANGED
@@ -165,7 +165,7 @@ For `#show` actions that simply present the model using a HTML page or a JSON or
165
165
 
166
166
  ```ruby
167
167
  def show
168
- present Comment::Create
168
+ present Comment::Update
169
169
  end
170
170
  ```
171
171
 
@@ -372,17 +372,17 @@ class Comment::Image::Crop < Trailblazer::Operation
372
372
  include Worker
373
373
 
374
374
  def process(params)
375
- # will be run asynchronous.
375
+ # will be run asynchronously.
376
376
  end
377
377
  end
378
378
  ```
379
379
 
380
380
  ### Rendering Operation's Form
381
381
 
382
- You have access to an operation's form using `::contract`.
382
+ You have access to an operation's form using `::present`.
383
383
 
384
384
  ```ruby
385
- Comment::Create.contract(params)
385
+ Comment::Create.present(params)
386
386
  ```
387
387
 
388
388
  This will run the operation's `#process` method _without_ the validate block and return the contract.
@@ -418,8 +418,15 @@ You can just add
418
418
  require 'trailblazer/autoloading'
419
419
  ```
420
420
 
421
- to `config/initializers/trailblazer.rb` and files will be "automatically" loaded.
421
+ to `config/initializers/trailblazer.rb` and implementation files like `Operation` will be automatically loaded.
422
422
 
423
+ In case you structure your CRUD operations in the `app/concepts/thing/crud.rb` file layout we use in the book, you're gonna run into `Missing constant` trouble. As the `crud.rb` files are not found by Rails it is a good idea to enable CRUD autoloading.
424
+
425
+ ```ruby
426
+ require trailblazer/crud_autoloading
427
+ ```
428
+
429
+ This will go through `app/concepts/`, find all the `crud.rb` files, autload their corresponding namespace (e.g. `Thing`, which is a model) and then load the `crud` file.
423
430
 
424
431
 
425
432
  ## Why?
data/TODO.md CHANGED
@@ -1,6 +1,11 @@
1
+ # Operation: External API
2
+
1
3
  * allow `Op[{body: "Great!"}, {additional: true}]` to save merge.
2
4
  * make `Op[]` not require wrap like `comment: {}`
3
5
  * in tests, make Op[].model return the reloaded model!
4
6
 
7
+ # Operation: Internal API
8
+
9
+ * don't populate the form in #present context, we don't need it (only the representer goes nuts)
5
10
  * don't pass contract in validate, we have #contract.
6
11
  * abstract validate/success/fail into methods to make it easily overrideable.
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- trailblazer (0.0.1)
4
+ trailblazer (0.1.1)
5
5
  actionpack (>= 3.0.0)
6
- reform (= 1.2.0.beta1)
6
+ reform (~> 1.2.0)
7
7
  representable (>= 2.1.1, < 2.2.0)
8
8
  uber (>= 0.0.10)
9
9
 
@@ -37,18 +37,18 @@ GEM
37
37
  celluloid (0.16.0)
38
38
  timers (~> 4.0.0)
39
39
  connection_pool (2.0.0)
40
- disposable (0.0.8)
40
+ disposable (0.0.9)
41
41
  representable (~> 2.0)
42
42
  uber
43
43
  erubis (2.7.0)
44
44
  hitimes (1.2.2)
45
45
  i18n (0.6.11)
46
46
  json (1.8.1)
47
- mini_portile (0.6.0)
47
+ mini_portile (0.6.1)
48
48
  minitest (5.4.2)
49
49
  multi_json (1.10.1)
50
- nokogiri (1.6.3.1)
51
- mini_portile (= 0.6.0)
50
+ nokogiri (1.6.4.1)
51
+ mini_portile (~> 0.6.0)
52
52
  rack (1.5.2)
53
53
  rack-test (0.6.2)
54
54
  rack (>= 1.0)
@@ -61,7 +61,7 @@ GEM
61
61
  redis (3.1.0)
62
62
  redis-namespace (1.5.1)
63
63
  redis (~> 3.0, >= 3.0.4)
64
- reform (1.2.0.beta1)
64
+ reform (1.2.1)
65
65
  activemodel
66
66
  disposable (~> 0.0.5)
67
67
  representable (~> 2.1.0)
@@ -83,7 +83,7 @@ GEM
83
83
  hitimes
84
84
  tzinfo (1.2.2)
85
85
  thread_safe (~> 0.1)
86
- uber (0.0.10)
86
+ uber (0.0.11)
87
87
 
88
88
  PLATFORMS
89
89
  ruby
@@ -0,0 +1,8 @@
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
@@ -4,6 +4,8 @@ require 'reform'
4
4
 
5
5
  # TODO: OP[] without wrapper, OP.run with (eg for params)
6
6
 
7
+ # to be documented: only implement #setup! when not using CRUD and when ::present needed (make example for simple Op without #setup!)
8
+
7
9
  module Trailblazer
8
10
  class Operation
9
11
  extend Uber::InheritableAttr
@@ -37,7 +39,7 @@ module Trailblazer
37
39
 
38
40
  # Runs #process without validate and returns the form object.
39
41
  def present(*params)
40
- build_operation_class(*params).new(:validate => false).run(*params).last
42
+ build_operation_class(*params).new.present(*params)
41
43
  end
42
44
 
43
45
  def contract(&block)
@@ -57,7 +59,6 @@ module Trailblazer
57
59
  def initialize(options={})
58
60
  @valid = true
59
61
  # DISCUSS: use reverse_merge here?
60
- @validate = options[:validate] == false ? false : true
61
62
  @raise_on_invalid = options[:raise_on_invalid] || false
62
63
  end
63
64
 
@@ -68,6 +69,13 @@ module Trailblazer
68
69
  [process(*params), valid?].reverse
69
70
  end
70
71
 
72
+ def present(*params)
73
+ setup!(*params)
74
+
75
+ @contract = contract_for(nil, @model)
76
+ self
77
+ end
78
+
71
79
  attr_reader :contract
72
80
 
73
81
  def valid?
@@ -77,14 +85,16 @@ module Trailblazer
77
85
  private
78
86
 
79
87
  def setup!(*params)
88
+ @model = model!(*params)
80
89
  end
81
90
 
82
- def validate(params, model, contract_class=nil) # NOT to be overridden?!! it creates Result for us.
91
+ # Implement #model! to find/create your operation model (if required).
92
+ def model!(*params)
93
+ end
83
94
 
95
+ def validate(params, model, contract_class=nil) # NOT to be overridden?!! it creates Result for us.
84
96
  @contract = contract_for(contract_class, model)
85
97
 
86
- return self unless @validate # Op.contract will return here.
87
-
88
98
  if @valid = contract.validate(params)
89
99
  yield contract if block_given?
90
100
  else
@@ -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.new(:validate => false).run(params).last # FIXME: make that available via Operation.
8
+ @operation = operation_class.new.present(params)
9
9
  @form = @operation.contract
10
10
  @model = @operation.model
11
11
 
@@ -63,7 +63,8 @@ private
63
63
  unless request.format == :html
64
64
  # this is what happens:
65
65
  # respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
66
- concept_name = operation_class.model_class # this could be renamed to ::concept_class soon.
66
+ concept_name = operation_class.model_class.to_s.underscore # this could be renamed to ::concept_class soon.
67
+
67
68
  params.merge!(concept_name => request.body.string)
68
69
  end
69
70
 
@@ -39,8 +39,8 @@ module Trailblazer
39
39
  end
40
40
 
41
41
  private
42
- def setup!(params)
43
- @model ||= instantiate_model(params)
42
+ def model!(params)
43
+ instantiate_model(params)
44
44
  end
45
45
 
46
46
  def instantiate_model(params)
@@ -1,18 +1,18 @@
1
1
  module Trailblazer::Operation::Representer
2
- # TODO: test me.
3
-
4
2
  def self.included(base)
5
3
  base.extend Uber::InheritableAttr
6
4
  base.inheritable_attr :representer_class
7
5
  # TODO: allow representer without contract?!
8
- # TODO: we have to extract the schema here, not subclass the contract.
9
- base.representer_class = Class.new(base.contract_class.representer_class)
10
6
  base.extend ClassMethods
11
7
  end
12
8
 
13
9
  module ClassMethods
14
10
  def representer(&block)
15
- representer_class.class_eval(&block)
11
+ build_representer_class.class_eval(&block)
12
+ end
13
+
14
+ def build_representer_class
15
+ representer_class || self.representer_class= Class.new(contract_class.schema)
16
16
  end
17
17
  end
18
18
  end
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -238,8 +238,7 @@ class OperationTest < MiniTest::Spec
238
238
  it { OperationReceivingLottaArguments.run(Object, {}).must_equal([true, [Object, {}]]) }
239
239
 
240
240
 
241
- # TODO: experimental.
242
- # ::present to avoid running #validate.
241
+ # ::present only runs #setup! which runs #model!.
243
242
  class ContractOnlyOperation < Trailblazer::Operation
244
243
  self.contract_class = class Contract
245
244
  def initialize(model)
@@ -249,12 +248,12 @@ class OperationTest < MiniTest::Spec
249
248
  self
250
249
  end
251
250
 
252
- def process(params)
253
- @object = Object # arbitrary init code.
251
+ def model!(params)
252
+ Object
253
+ end
254
254
 
255
- validate(params, Object) do
256
- raise "this should not be run."
257
- end
255
+ def process(params)
256
+ raise "This is not run!"
258
257
  end
259
258
  end
260
259
 
@@ -34,6 +34,7 @@ class ProcessParamsTest < ActionController::TestCase
34
34
 
35
35
  test "Create with overridden #process_params" do
36
36
  post :create, band: {name: "Kreator"}
37
+ assert_redirected_to band_path(Band.last)
37
38
 
38
39
  band = Band.last
39
40
  assert_equal "Kreator", band.name
@@ -44,10 +45,6 @@ end
44
45
  class ResponderRespondTest < ActionController::TestCase
45
46
  tests SongsController
46
47
 
47
- setup do
48
- @routes = Rails.application.routes
49
- end
50
-
51
48
  # HTML
52
49
  # #respond Create [valid]
53
50
  test "Create [html/valid]" do
@@ -57,6 +54,7 @@ class ResponderRespondTest < ActionController::TestCase
57
54
 
58
55
  test "Create [html/invalid]" do
59
56
  post :create, {song: {title: ""}}
57
+ assert_response 200
60
58
  assert_equal @response.body, "{:title=&gt;[&quot;can&#39;t be blank&quot;]}"
61
59
  end
62
60
 
@@ -69,11 +67,13 @@ class ResponderRespondTest < ActionController::TestCase
69
67
 
70
68
  test "respond with block [html/valid]" do
71
69
  post :create_with_block, {song: {title: "You're Going Down"}}
70
+ assert_response 200
72
71
  assert_equal "block run, valid: true", response.body
73
72
  end
74
73
 
75
74
  test "respond with block [html/invalid]" do
76
75
  post :create_with_block, {song: {title: ""}}
76
+ assert_response 200
77
77
  assert_equal "block run, valid: false", response.body
78
78
  end
79
79
 
@@ -100,18 +100,17 @@ class ResponderRespondTest < ActionController::TestCase
100
100
  assert_response 200
101
101
  assert_equal "Song slayer!", response.body
102
102
  end
103
+ end
103
104
 
104
- # TODO: #present
105
- # TODO: #run
106
-
107
- # describe "#run" do
108
- # test "#run" do
109
-
110
- # end
111
- # end
112
-
113
-
105
+ class ResponderRespondWithJSONTest < ActionController::TestCase
106
+ tests BandsController
114
107
 
108
+ # JSON
109
+ test "Create [JSON/valid]" do
110
+ post :create, {name: "SNFU"}.to_json, format: :json
111
+ assert_response 201
112
+ assert_equal "SNFU", Band.last.name
113
+ end
115
114
  end
116
115
 
117
116
 
@@ -144,7 +143,7 @@ class ControllerPresentTest < ActionController::TestCase
144
143
 
145
144
  get :show, id: band.id
146
145
 
147
- assert_equal "bands/show.html: Band,Band,true,Band::Create,Essen\n", response.body
146
+ assert_equal "bands/show.html: Band,Band,true,Band::Update,Essen\n", response.body
148
147
  end
149
148
 
150
149
  test "#present [JSON]" do
@@ -46,9 +46,9 @@ class BandsController < ApplicationController
46
46
  respond_to :html, :json
47
47
 
48
48
  def show
49
- present Band::Create do |op|
49
+ present Band::Update do |op|
50
50
  @klass = op.model.class
51
- @locality = params[:band][:locality]
51
+ @locality = params[:band][:locality] unless params[:format] == "json"
52
52
  end # respond_to
53
53
  end
54
54
 
@@ -95,6 +95,8 @@ ERB
95
95
 
96
96
  private
97
97
  def process_params!(params) # this is where you set :current_user, etc.
98
+ return if params[:format] == "json"
99
+
98
100
  params[:band] ||= {}
99
101
  params[:band][:locality] = "Essen"
100
102
  end
@@ -2,11 +2,23 @@
2
2
  class Song < ActiveRecord::Base
3
3
  end
4
4
 
5
+ class Band < ActiveRecord::Base
6
+ has_many :songs
7
+ end
8
+
5
9
  # migrations
6
10
  class CreateAllTables < ActiveRecord::Migration
7
11
  def self.up
8
- create_table(:songs) { |t| t.string :title; t.integer :length }
9
- create_table(:bands) { |t| t.string :name; t.string :locality }
12
+ create_table(:songs) do |t|
13
+ t.string :title
14
+ t.integer :length
15
+ t.integer :band_id
16
+ end
17
+
18
+ create_table(:bands) do |t|
19
+ t.string :name
20
+ t.string :locality
21
+ end
10
22
  end
11
23
  end
12
24
  ActiveRecord::Migration.verbose = false
@@ -30,7 +30,7 @@ end
30
30
 
31
31
  class Band < ActiveRecord::Base
32
32
  class Create < Trailblazer::Operation
33
- include CRUD, Responder
33
+ include CRUD, Responder, Representer
34
34
  model Band, :create
35
35
 
36
36
  contract do
@@ -39,6 +39,14 @@ class Band < ActiveRecord::Base
39
39
 
40
40
  property :name, validates: {presence: true}
41
41
  property :locality
42
+
43
+ # class: Song #=> always create new song
44
+ # instance: { Song.find(params[:id]) or Song.new } # same as find_or_create ?
45
+ # this is what i want:
46
+ # maybe make populate_if_empty a representable feature?
47
+ collection :songs, populate_if_empty: Song do
48
+ property :title
49
+ end
42
50
  end
43
51
 
44
52
  def process(params)
@@ -48,12 +56,28 @@ class Band < ActiveRecord::Base
48
56
  end
49
57
 
50
58
  class JSON < self
51
- action :find
52
- include Representer
53
- # self.representer_class = Class.new(contract_class)
54
- # representer_class do
55
- # include Reform::Form::JSON
56
- # end
59
+ require "reform/form/json"
60
+ contract do
61
+ include Reform::Form::JSON # this allows deserialising JSON.
62
+ end
63
+
64
+ representer do
65
+ collection :songs, inherit: true, render_empty: false # tested in ControllerPresentTest.
66
+ end
67
+ end
68
+
69
+ builds do |params|
70
+ JSON if params[:format] == "json"
71
+ end
72
+ end
73
+
74
+ class Update < Create
75
+ action :update
76
+
77
+ # TODO: infer stuff per default.
78
+ class JSON < self
79
+ self.contract_class = Create::JSON.contract_class
80
+ self.representer_class = Create::JSON.representer_class
57
81
  end
58
82
 
59
83
  builds do |params|
@@ -0,0 +1,23 @@
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
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "actionpack", '>= 3.0.0' # this framework works on Rails.
22
22
  spec.add_dependency "uber", ">= 0.0.10" # no builder inheritance.
23
23
  spec.add_dependency "representable", ">= 2.1.1", "<2.2.0" # Representable::apply.
24
- spec.add_dependency "reform", "1.2.0.beta1"
24
+ spec.add_dependency "reform", "~> 1.2.0"
25
25
 
26
26
  spec.add_development_dependency "bundler", "~> 1.3"
27
27
  spec.add_development_dependency "rake"
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.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-04 00:00:00.000000000 Z
11
+ date: 2014-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -62,16 +62,16 @@ dependencies:
62
62
  name: reform
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - '='
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 1.2.0.beta1
67
+ version: 1.2.0
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - '='
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 1.2.0.beta1
74
+ version: 1.2.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: bundler
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -151,6 +151,7 @@ files:
151
151
  - gemfiles/Gemfile.rails.lock
152
152
  - lib/trailblazer.rb
153
153
  - lib/trailblazer/autoloading.rb
154
+ - lib/trailblazer/crud_autoloading.rb
154
155
  - lib/trailblazer/operation.rb
155
156
  - lib/trailblazer/operation/controller.rb
156
157
  - lib/trailblazer/operation/crud.rb
@@ -173,6 +174,7 @@ files:
173
174
  - test/rails/fake_app/song/operations.rb
174
175
  - test/rails/fake_app/views/bands/show.html.erb
175
176
  - test/rails/fake_app/views/songs/new.html.erb
177
+ - test/rails/integration_test.rb
176
178
  - test/rails/test_helper.rb
177
179
  - test/responder_test.rb
178
180
  - test/test_helper.rb
@@ -218,6 +220,7 @@ test_files:
218
220
  - test/rails/fake_app/song/operations.rb
219
221
  - test/rails/fake_app/views/bands/show.html.erb
220
222
  - test/rails/fake_app/views/songs/new.html.erb
223
+ - test/rails/integration_test.rb
221
224
  - test/rails/test_helper.rb
222
225
  - test/responder_test.rb
223
226
  - test/test_helper.rb