trailblazer 1.0.0.rc2 → 1.0.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.
@@ -1,144 +0,0 @@
1
- require 'trailblazer/autoloading'
2
-
3
- class Song < ActiveRecord::Base
4
- class Create < Trailblazer::Operation
5
- include Model
6
- include Responder
7
- model Song, :create
8
-
9
-
10
- contract do
11
- property :title, validates: {presence: true}
12
- property :length
13
- end
14
-
15
- def process(params)
16
- validate(params[:song]) do
17
- contract.save
18
- end
19
- end
20
-
21
-
22
- class Json < Create
23
- def process(params)
24
- @model = Song.create(JSON.parse(params[:song]))
25
- end
26
- end
27
- end
28
-
29
-
30
- class Delete < Create
31
- action :find
32
-
33
- def process(params)
34
- model.destroy
35
- self
36
- end
37
- end
38
- end
39
-
40
- class Band < ActiveRecord::Base
41
- class Create < Trailblazer::Operation
42
- include Model, Responder#, Representer
43
- model Band, :create
44
-
45
- contract do
46
- include Reform::Form::ActiveModel
47
- model Band
48
-
49
- property :name, validates: {presence: true}
50
- property :locality, prepopulator: ->(*) { self.locality = "Sydney" }
51
-
52
- # class: Song #=> always create new song
53
- # instance: { Song.find(params[:id]) or Song.new } # same as find_or_create ?
54
- # this is what i want:
55
- # maybe make populate_if_empty a representable feature?
56
- collection :songs, populate_if_empty: Song do
57
- property :title
58
- end
59
- end
60
-
61
- def process(params)
62
- validate(params[:band]) do
63
- contract.save
64
- end
65
- end
66
-
67
- require "representable/json"
68
- class JSON < self
69
- include Representer
70
-
71
- representer do
72
- collection :songs, inherit: true, render_empty: false # tested in ControllerPresentTest.
73
- end
74
- end
75
-
76
- class Admin < self
77
- def process(params)
78
- res = super
79
- model.update_attribute :name, "#{model.name} [ADMIN]"
80
- res
81
- end
82
- end
83
-
84
- # TODO: wait for uber 0.0.10 and @dutow.
85
- # builds -> (params) do
86
- # return JSON if params[:format] == "json"
87
- # return Admin if params[:admin]
88
- # end
89
- builds do |params|
90
- if params[:format] == "json"
91
- JSON
92
- elsif params[:admin]
93
- Admin
94
- end
95
- end
96
- end
97
-
98
- class Update < Create
99
- action :update
100
-
101
- # TODO: infer stuff per default.
102
- class JSON < self
103
- include Representer
104
- self.contract_class = Create::JSON.contract_class
105
- self.representer_class = Create::JSON.representer_class
106
- end
107
-
108
- builds do |params|
109
- JSON if params[:format] == "json"
110
- end
111
- end
112
-
113
- class Index < Trailblazer::Operation
114
- include Collection
115
-
116
- def model!(params)
117
- Band.all
118
- end
119
-
120
- builds do |params|
121
- JSON if params[:format] == "json"
122
- end
123
-
124
- class JSON < self
125
- include Representer
126
-
127
- module BandRepresenter
128
- include Representable::JSON
129
- property :name
130
- property :locality
131
- end
132
-
133
- self.representer_class = BandRepresenter
134
- end
135
- end
136
- end
137
-
138
- class Tenant < ActiveRecord::Base
139
- class Show < Trailblazer::Operation
140
- include Model
141
- model Tenant, :update
142
- end
143
- end
144
-
@@ -1 +0,0 @@
1
- bands/index.html: <% @collection.each do |element| %><%= "#{element.name} " %><% end %>
@@ -1,2 +0,0 @@
1
- OTHER SONG
2
- <%= @form.errors.to_s %>
@@ -1 +0,0 @@
1
- <%= @form.errors.to_s %>
@@ -1,95 +0,0 @@
1
- require "test_helper"
2
-
3
- class ResponderRespondTest < ActionController::TestCase
4
- tests SongsController
5
-
6
- # HTML
7
- # #respond Create [valid]
8
- test "Create [html/valid]" do
9
- post :create, {song: {title: "You're Going Down"}}
10
- assert_redirected_to song_path(Song.last)
11
- end
12
-
13
- test "Create [html/valid/location]" do
14
- post :other_create, {song: {title: "You're Going Down"}}
15
- assert_redirected_to other_create_songs_path
16
- end
17
-
18
- test "Create [html/invalid]" do
19
- post :create, {song: {title: ""}}
20
- assert_response 200
21
- assert_equal @response.body, "{:title=&gt;[&quot;can&#39;t be blank&quot;]}"
22
- end
23
-
24
- test "Create [html/invalid/action]" do
25
- post :other_create, {song: {title: ""}}
26
- assert_response 200
27
- assert_equal @response.body, "OTHER SONG\n{:title=&gt;[&quot;can&#39;t be blank&quot;]}\n"
28
- assert_template "songs/another_view"
29
- end
30
-
31
- test "Delete [html/valid]" do
32
- song = Song::Create[song: {title: "You're Going Down"}].model
33
- delete :destroy, id: song.id
34
- assert_redirected_to songs_path
35
- # assert that model is deleted.
36
- end
37
-
38
- test "respond with block [html/valid]" do
39
- post :create_with_block, {song: {title: "You're Going Down"}}
40
- assert_response 200
41
- assert_equal "block run, valid: true", response.body
42
- end
43
-
44
- test "respond with block [html/invalid]" do
45
- post :create_with_block, {song: {title: ""}}
46
- assert_response 200
47
- assert_equal "block run, valid: false", response.body
48
- end
49
-
50
- # JSON
51
- test "Delete [json/valid]" do
52
- song = Song::Create[song: {title: "You're Going Down"}].model
53
- delete :destroy, id: song.id, format: :json
54
- assert_response 204 # no content.
55
- end
56
-
57
- # JS
58
- test "Delete [js/valid]" do
59
- song = Song::Create[song: {title: "You're Going Down"}].model
60
- assert_raises ActionView::MissingTemplate do
61
- # js wants to render destroy.js.erb
62
- delete :destroy, id: song.id, format: :js
63
- end
64
- end
65
-
66
- test "Delete with formats [js/valid]" do
67
- song = Song::Create[song: {title: "You're Going Down"}].model
68
-
69
- delete :destroy_with_formats, id: song.id, format: :js
70
- assert_response 200
71
- assert_equal "Song slayer!", response.body
72
- end
73
- end
74
-
75
- class ResponderRespondWithJSONTest < ActionController::TestCase
76
- tests BandsController
77
-
78
- # JSON
79
- test "Create [JSON/valid]" do
80
- post :create, {name: "SNFU"}.to_json, format: :json
81
- assert_response 201
82
- assert_equal "SNFU", Band.last.name
83
- end
84
- end
85
-
86
- # TODO: merge with above tests on SongsController.
87
- class ControllerRespondTest < ActionController::TestCase
88
- tests BandsController
89
-
90
- test "#respond with builds" do
91
- post :create, band: {name: "SNFU"}, admin: true
92
- assert_response 302
93
- assert_equal "SNFU [ADMIN]", Band.last.name
94
- end
95
- end
@@ -1,9 +0,0 @@
1
- require 'trailblazer'
2
- require 'minitest/autorun'
3
-
4
- require "reform/form/active_model/validations"
5
- Reform::Form.class_eval do
6
- include Reform::Form::ActiveModel::Validations
7
- end
8
-
9
- require 'fake_app/rails_app.rb'
@@ -1,75 +0,0 @@
1
- require 'test_helper'
2
- require 'trailblazer/operation/responder'
3
-
4
- class Song
5
- extend ActiveModel::Naming
6
-
7
- class Operation < Trailblazer::Operation
8
- include Model
9
- model Song
10
- include Responder
11
-
12
- def process(params)
13
- invalid! if params == false
14
- end
15
- end
16
- end
17
-
18
- module MyApp
19
- class Song
20
- extend ActiveModel::Naming
21
-
22
- class Operation < Trailblazer::Operation
23
- include Model
24
- include Responder
25
- model Song
26
-
27
- def process(params)
28
- invalid! if params == false
29
- end
30
- end
31
- end
32
- end
33
-
34
- class ResponderTestForModelWithoutNamespace < MiniTest::Spec
35
-
36
- # test ::model_name
37
- it { Song::Operation.model_name.name.must_equal "Song" }
38
- it { Song::Operation.model_name.singular.must_equal "song" }
39
- it { Song::Operation.model_name.plural.must_equal "songs" }
40
- it { Song::Operation.model_name.element.must_equal "song" }
41
- it { Song::Operation.model_name.human.must_equal "Song" }
42
- it { Song::Operation.model_name.collection.must_equal "songs" }
43
- it { Song::Operation.model_name.param_key.must_equal "song" }
44
- it { Song::Operation.model_name.i18n_key.must_equal :"song" }
45
- it { Song::Operation.model_name.route_key.must_equal "songs" }
46
- it { Song::Operation.model_name.singular_route_key.must_equal "song" }
47
-
48
- # #errors
49
- it { Song::Operation.(true).errors.must_equal [] }
50
- it { Song::Operation.(false).errors.must_equal [1] } # TODO: since we don't want responder to render anything, just return _one_ error. :)
51
-
52
- # TODO: integration test with Controller.
53
- end
54
-
55
-
56
- class ResponderTestForModelWitNamespace < MiniTest::Spec
57
-
58
- # test ::model_name
59
- it { MyApp::Song::Operation.model_name.name.must_equal "MyApp::Song" }
60
- it { MyApp::Song::Operation.model_name.singular.must_equal "my_app_song" }
61
- it { MyApp::Song::Operation.model_name.plural.must_equal "my_app_songs" }
62
- it { MyApp::Song::Operation.model_name.element.must_equal "song" }
63
- it { MyApp::Song::Operation.model_name.human.must_equal "Song" }
64
- it { MyApp::Song::Operation.model_name.collection.must_equal "my_app/songs" }
65
- it { MyApp::Song::Operation.model_name.param_key.must_equal "my_app_song" } # "song" for AR.
66
- it { MyApp::Song::Operation.model_name.i18n_key.must_equal :"my_app/song" }
67
- it { MyApp::Song::Operation.model_name.route_key.must_equal "my_app_songs" } # "songs" for AR.
68
- it { MyApp::Song::Operation.model_name.singular_route_key.must_equal "my_app_song" } # "song" for AR.
69
-
70
- # #errors
71
- it { MyApp::Song::Operation.(true).errors.must_equal [] }
72
- it { MyApp::Song::Operation.(false).errors.must_equal [1] } # TODO: since we don't want responder to render anything, just return _one_ error. :)
73
-
74
- # TODO: integration test with Controller.
75
- end
@@ -1,85 +0,0 @@
1
- require 'test_helper'
2
- require 'trailblazer/operation/uploaded_file'
3
-
4
- class TempfileTest < MiniTest::Spec
5
-
6
- end
7
-
8
- class UploadedFileTest < MiniTest::Spec
9
- let (:image) { File.open("test/fixtures/apotomo.png") }
10
- let (:tempfile) { tmp = Tempfile.new("bla")
11
- tmp.write image.read
12
- tmp
13
- }
14
-
15
- let (:upload) { ActionDispatch::Http::UploadedFile.new(
16
- tempfile: tempfile,
17
- filename: "apotomo.png",
18
- type: "image/png")
19
- }
20
-
21
- describe "#to_hash" do
22
- before {
23
- @uploaded_path = upload.tempfile.path
24
- @subject = Trailblazer::Operation::UploadedFile.new(upload).to_hash
25
- }
26
-
27
- it { @subject[:filename].must_equal "apotomo.png" }
28
- it { @subject[:type].must_equal "image/png" }
29
- it { @subject[:tempfile_path].must_match /\w+_trailblazer_upload$/ }
30
-
31
-
32
- # Rails upload file must be removed.
33
- it {
34
- File.exists?(@uploaded_path).must_equal false }
35
-
36
- it { File.exists?(@subject[:tempfile_path]).must_equal true }
37
- it { File.size(@subject[:tempfile_path]).must_equal image.size }
38
- end
39
-
40
- describe "::from_hash" do
41
- let (:data) { Trailblazer::Operation::UploadedFile.new(upload).to_hash }
42
- subject { Trailblazer::Operation::UploadedFile.from_hash(data) }
43
-
44
-
45
- it { subject.original_filename.must_equal "apotomo.png" }
46
- it { subject.content_type.must_equal "image/png" }
47
- it { subject.tempfile.must_be_kind_of File }
48
- it { subject.size.must_equal image.size }
49
-
50
- # params is not modified.
51
- it { params = data.clone and subject; data.must_equal params }
52
-
53
- # Tempfile must have proper extension for further processing (sidekiq/imagemagick, etc).
54
- it { subject.tempfile.path.must_match /\.png$/ }
55
-
56
- # Tempfile must be unlinked after process is finished.
57
- it do
58
- @subject = Trailblazer::Operation::UploadedFile.from_hash(data)
59
-
60
- processable_file = @subject.tempfile.path
61
- File.exists?(processable_file).must_equal true # this file must be GCed since it's a Tempfile, that's the whole point.
62
- # @subject = nil
63
- # GC.start
64
- # File.exists?(processable_file).must_equal false
65
- end
66
- end
67
-
68
-
69
- describe "with custom tmp directory" do
70
- describe "#to_hash" do
71
- before {
72
- @uploaded = Trailblazer::Operation::UploadedFile.new(upload, tmp_dir: tmp_dir)
73
- @subject = @uploaded.to_hash[:tempfile_path]
74
- }
75
-
76
- it { @subject.must_match /\w+_trailblazer_upload$/ }
77
- it { @subject.must_match /^\/tmp\/uploads\// }
78
-
79
- it { File.exists?(@subject).must_equal true }
80
- it { File.size(@subject).must_equal image.size }
81
-
82
- it { @uploaded.instance_variable_get(:@with_tmp_dir).path.must_equal nil }
83
- end
84
- end
85
- end
data/test/worker_test.rb DELETED
@@ -1,124 +0,0 @@
1
- # make sure #run always returns model
2
-
3
- # in test with sidekiq/testing
4
- # Operation.run #=> call perform_one and return [result, model] (model?)
5
-
6
- require 'test_helper'
7
- require 'trailblazer/operation'
8
- require 'trailblazer/operation/worker'
9
- require 'sidekiq/testing'
10
-
11
-
12
- class WorkerTest < MiniTest::Spec
13
- class Operation < Trailblazer::Operation
14
- include Worker
15
-
16
- def process(params)
17
- with_symbol = params[:title]
18
- with_string = params["title"]
19
- @model = "I was working hard on #{params.inspect}. title:#{with_symbol} \"title\"=>#{with_string}"
20
- end
21
- end
22
-
23
- class NoBackgroundOperation < Operation
24
- def self.background?
25
- false
26
- end
27
- end
28
-
29
- # test basic worker functionality.
30
- describe "with sidekiq ss" do
31
- it do
32
- res = Operation.run(title: "Dragonfly")
33
-
34
- res.kind_of?(String).must_equal true # for now, we return the job from sidekiq
35
- Operation.jobs[0]["args"].must_equal([{"title"=>"Dragonfly"}])
36
- Operation.perform_one.last.model.must_equal "I was working hard on {\"title\"=>\"Dragonfly\"}. title:Dragonfly \"title\"=>Dragonfly"
37
- end
38
- end
39
-
40
- # without sidekiq, we don't have indifferent_access automatically.
41
- it { NoBackgroundOperation.run(title: "Dragonfly").last.model.must_equal "I was working hard on {:title=>\"Dragonfly\"}. title:Dragonfly \"title\"=>" }
42
-
43
-
44
- # test manual serialisation (to be done with UploadedFile etc automatically).
45
- class SerializingOperation < Operation
46
- include Worker
47
-
48
- def self.serializable(params)
49
- {wrap: params}
50
- end
51
-
52
- def deserializable(params)
53
- params[:wrap]
54
- end
55
- end
56
-
57
- describe "with serialization in sidekiq" do
58
- before { @res = SerializingOperation.run(title: "Dragonfly") }
59
-
60
- it { @res.kind_of?(String).must_equal true } # for now, we return the job from sidekiq.
61
- it { SerializingOperation.jobs[0]["args"].must_equal([{"wrap"=>{"title"=>"Dragonfly"}}]) }
62
- it { SerializingOperation.perform_one.last.model.must_equal "I was working hard on {\"title\"=>\"Dragonfly\"}. title:Dragonfly \"title\"=>Dragonfly" }
63
- end
64
- end
65
-
66
-
67
- require "trailblazer/operation/uploaded_file"
68
- require "action_dispatch/http/upload"
69
- class WorkerFileMarshallerTest < MiniTest::Spec
70
- def uploaded_file(name)
71
- tmp = Tempfile.new("bla")
72
- tmp.write File.open("test/fixtures/#{name}").read
73
-
74
- ActionDispatch::Http::UploadedFile.new(
75
- tempfile: tmp,
76
- filename: name,
77
- type: "image/png")
78
- end
79
-
80
- class Operation < Trailblazer::Operation
81
- contract do
82
- property :title
83
- property :image, file: true
84
-
85
- property :album do
86
- property :image, file: true
87
- end
88
- end
89
-
90
- include Worker
91
- include Worker::FileMarshaller # should be ContractFileMarshaller
92
-
93
- def process(params)
94
- @params = params
95
- end
96
-
97
- attr_reader :params
98
- end
99
-
100
- # TODO: no image
101
-
102
- # with image serializes the file for later retrieval.
103
- it do
104
- Operation.run(title: "Dragonfly", image: uploaded_file("apotomo.png"), album: {image: uploaded_file("cells.png")})
105
-
106
- args = Operation.jobs[0]["args"].first
107
- args["title"].must_equal("Dragonfly")
108
- args["image"]["filename"].must_equal "apotomo.png"
109
- args["image"]["tempfile_path"].must_match /trailblazer_upload/
110
-
111
- args["album"]["image"]["filename"].must_equal "cells.png"
112
-
113
- _, op = Operation.perform_one # deserialize.
114
-
115
- params = op.params
116
-
117
- params["title"].must_equal("Dragonfly")
118
- params[:title].must_equal("Dragonfly") # must allow indifferent_access.
119
- params["image"].must_be_kind_of ActionDispatch::Http::UploadedFile
120
- params["image"].original_filename.must_equal "apotomo.png"
121
- params["album"]["image"].must_be_kind_of ActionDispatch::Http::UploadedFile
122
- params["album"]["image"].original_filename.must_equal "cells.png"
123
- end
124
- end