trailblazer 0.1.0 → 0.1.2

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