azeroth 0.6.3 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +37 -2
  3. data/.rubocop.yml +11 -0
  4. data/Dockerfile +2 -2
  5. data/README.md +164 -1
  6. data/azeroth.gemspec +6 -5
  7. data/lib/azeroth.rb +1 -0
  8. data/lib/azeroth/decorator.rb +123 -0
  9. data/lib/azeroth/decorator/key_value_extractor.rb +2 -2
  10. data/lib/azeroth/decorator/options.rb +3 -3
  11. data/lib/azeroth/model.rb +3 -1
  12. data/lib/azeroth/options.rb +38 -1
  13. data/lib/azeroth/request_handler.rb +39 -2
  14. data/lib/azeroth/request_handler/create.rb +31 -5
  15. data/lib/azeroth/request_handler/update.rb +3 -3
  16. data/lib/azeroth/resourceable.rb +27 -0
  17. data/lib/azeroth/resourceable/class_methods.rb +120 -6
  18. data/lib/azeroth/routes_builder.rb +2 -1
  19. data/lib/azeroth/version.rb +1 -1
  20. data/spec/controllers/pokemon_masters_controller_spec.rb +56 -0
  21. data/spec/controllers/pokemons_controller_spec.rb +56 -0
  22. data/spec/dummy/app/controllers/games_controller.rb +22 -0
  23. data/spec/dummy/app/controllers/pokemon_masters_controller.rb +9 -0
  24. data/spec/dummy/app/controllers/pokemons_controller.rb +27 -0
  25. data/spec/dummy/app/controllers/publishers_controller.rb +8 -0
  26. data/spec/dummy/app/models/game.rb +5 -0
  27. data/spec/dummy/app/models/game/decorator.rb +9 -0
  28. data/spec/dummy/app/models/name_decorator.rb +5 -0
  29. data/spec/dummy/app/models/pokemon.rb +8 -0
  30. data/spec/dummy/app/models/pokemon/decorator.rb +16 -0
  31. data/spec/dummy/app/models/pokemon/favorite_decorator.rb +7 -0
  32. data/spec/dummy/app/models/pokemon_master.rb +7 -0
  33. data/spec/dummy/app/models/pokemon_master/decorator.rb +17 -0
  34. data/spec/dummy/app/models/publisher.rb +5 -0
  35. data/spec/dummy/config/routes.rb +8 -0
  36. data/spec/dummy/db/schema.rb +26 -0
  37. data/spec/integration/readme/azeroth/decorator_spec.rb +48 -0
  38. data/spec/integration/readme/controllers/games_controller_spec.rb +42 -0
  39. data/spec/integration/yard/azeroth/decorator_spec.rb +58 -17
  40. data/spec/integration/yard/controllers/games_controller_spec.rb +42 -0
  41. data/spec/lib/azeroth/decorator/key_value_extractor_spec.rb +32 -0
  42. data/spec/lib/azeroth/decorator_spec.rb +28 -2
  43. data/spec/lib/azeroth/request_handler/create_spec.rb +166 -0
  44. data/spec/lib/azeroth/request_handler/update_spec.rb +91 -0
  45. data/spec/lib/azeroth/request_handler_spec.rb +3 -1
  46. data/spec/support/app/controllers/request_handler_controller.rb +12 -1
  47. data/spec/support/factories/game.rb +8 -0
  48. data/spec/support/factories/pokemon.rb +8 -0
  49. data/spec/support/factories/pokemon_master.rb +9 -0
  50. data/spec/support/factories/publisher.rb +7 -0
  51. data/spec/support/shared_examples/request_handler.rb +5 -2
  52. metadata +70 -12
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe GamesController, controller: true do
6
+ describe 'yard' do
7
+ describe 'POST create' do
8
+ it 'create game' do
9
+ post '/publishers.json', params: {
10
+ publisher: {
11
+ name: 'Nintendo'
12
+ }
13
+ }
14
+
15
+ publisher = JSON.parse(response.body)
16
+ expect(publisher)
17
+ .to eq({
18
+ 'id' => publisher['id'],
19
+ 'name' => 'Nintendo'
20
+ })
21
+
22
+ publisher = Publisher.last
23
+ post "/publishers/#{publisher['id']}/games.json", params: {
24
+ game: {
25
+ name: 'Pokemon'
26
+ }
27
+ }
28
+
29
+ game = Game.last
30
+
31
+ expect(JSON.parse(response.body))
32
+ .to eq({
33
+ 'id' => game.id,
34
+ 'name' => 'Pokemon',
35
+ 'publisher' => {
36
+ 'name' => 'Nintendo'
37
+ }
38
+ })
39
+ end
40
+ end
41
+ end
42
+ end
@@ -227,5 +227,37 @@ describe Azeroth::Decorator::KeyValueExtractor do
227
227
  end
228
228
  end
229
229
  end
230
+
231
+ context 'when value is nil' do
232
+ context 'without decorator options' do
233
+ let(:object) { build(:document, name: nil) }
234
+
235
+ it 'returns nil for value' do
236
+ expect(extractor.as_json)
237
+ .to eq({ 'name' => nil })
238
+ end
239
+ end
240
+
241
+ context 'with decorator false' do
242
+ let(:object) { build(:document, name: nil) }
243
+ let(:options_hash) { { decorator: false } }
244
+
245
+ it 'returns nil for value' do
246
+ expect(extractor.as_json)
247
+ .to eq({ 'name' => nil })
248
+ end
249
+ end
250
+
251
+ context 'with decorator option' do
252
+ let(:object) { create(:pokemon_master) }
253
+ let(:options_hash) { { decorator: Pokemon::Decorator } }
254
+ let(:attribute) { :favorite_pokemon }
255
+
256
+ it 'returns nil for value' do
257
+ expect(extractor.as_json)
258
+ .to eq({ 'favorite_pokemon' => nil })
259
+ end
260
+ end
261
+ end
230
262
  end
231
263
  end
@@ -62,6 +62,12 @@ describe Azeroth::Decorator do
62
62
  end
63
63
 
64
64
  describe '#as_json' do
65
+ context 'when object is nil' do
66
+ let(:object) { nil }
67
+
68
+ it { expect(decorator.as_json).to be_nil }
69
+ end
70
+
65
71
  context 'when object is just a model' do
66
72
  let(:expected_json) do
67
73
  {
@@ -166,7 +172,7 @@ describe Azeroth::Decorator do
166
172
  end
167
173
  end
168
174
 
169
- context 'with decotator for model with validation' do
175
+ context 'with decorator for model with validation' do
170
176
  subject(:decorator) do
171
177
  Document::DecoratorWithError.new(object)
172
178
  end
@@ -255,6 +261,26 @@ describe Azeroth::Decorator do
255
261
  expect(decorator.as_json).to eq(expected_json)
256
262
  end
257
263
  end
264
+
265
+ context 'with nil value and defined decorator' do
266
+ subject(:decorator) do
267
+ Factory::DecoratorWithProduct.new(factory)
268
+ end
269
+
270
+ let(:factory) { create(:factory) }
271
+
272
+ let(:expected_json) do
273
+ {
274
+ name: factory.name,
275
+ main_product: nil,
276
+ products: []
277
+ }.deep_stringify_keys
278
+ end
279
+
280
+ it 'exposes relation' do
281
+ expect(decorator.as_json).to eq(expected_json)
282
+ end
283
+ end
258
284
  end
259
285
  end
260
286
 
@@ -262,7 +288,7 @@ describe Azeroth::Decorator do
262
288
  subject(:decorator) { decorator_class.new(object) }
263
289
 
264
290
  let(:decorator_class) { Class.new(described_class) }
265
- let(:model) { build(:dummy_model) }
291
+ let(:model) { build(:dummy_model) }
266
292
 
267
293
  it 'delegates methods to object' do
268
294
  expect(decorator.first_name).not_to be_nil
@@ -23,6 +23,172 @@ describe Azeroth::RequestHandler::Create do
23
23
  .by(1)
24
24
  end
25
25
  end
26
+
27
+ context 'with before_save block option' do
28
+ it_behaves_like 'a request handler', status: :created do
29
+ let(:block) do
30
+ value = 10
31
+ proc do
32
+ document.reference = "X-MAGIC-#{value}"
33
+ end
34
+ end
35
+
36
+ let(:options_hash) do
37
+ {
38
+ before_save: block
39
+ }
40
+ end
41
+
42
+ let(:extra_params) do
43
+ {
44
+ document: {
45
+ name: 'My Document'
46
+ }
47
+ }
48
+ end
49
+
50
+ let(:expected_json) do
51
+ {
52
+ 'name' => 'My Document',
53
+ 'reference' => 'X-MAGIC-10'
54
+ }
55
+ end
56
+
57
+ it 'creates entry' do
58
+ expect { handler.process }
59
+ .to change(Document, :count)
60
+ .by(1)
61
+ end
62
+
63
+ it 'changes entry before saving' do
64
+ handler.process
65
+
66
+ expect(Document.last.reference)
67
+ .to eq('X-MAGIC-10')
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'with before_save symbol option' do
73
+ it_behaves_like 'a request handler', status: :created do
74
+ let(:options_hash) do
75
+ {
76
+ before_save: :add_magic_reference
77
+ }
78
+ end
79
+
80
+ let(:extra_params) do
81
+ {
82
+ document: {
83
+ name: 'My Document'
84
+ }
85
+ }
86
+ end
87
+
88
+ let(:expected_json) do
89
+ {
90
+ 'name' => 'My Document',
91
+ 'reference' => 'X-MAGIC-15'
92
+ }
93
+ end
94
+
95
+ it 'creates entry' do
96
+ expect { handler.process }
97
+ .to change(Document, :count)
98
+ .by(1)
99
+ end
100
+
101
+ it 'changes entry before saving' do
102
+ handler.process
103
+
104
+ expect(Document.last.reference)
105
+ .to eq('X-MAGIC-15')
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'with build_with as symbol' do
112
+ it_behaves_like 'a request handler', status: :created do
113
+ let(:options_hash) do
114
+ {
115
+ build_with: :build_magic_document
116
+ }
117
+ end
118
+
119
+ let(:extra_params) do
120
+ {
121
+ document: {
122
+ name: 'My Document'
123
+ }
124
+ }
125
+ end
126
+
127
+ let(:expected_json) do
128
+ {
129
+ 'name' => 'My Document',
130
+ 'reference' => 'X-MAGIC-15'
131
+ }
132
+ end
133
+
134
+ it 'creates entry' do
135
+ expect { handler.process }
136
+ .to change(Document, :count)
137
+ .by(1)
138
+ end
139
+
140
+ it 'builds entity with custom method' do
141
+ handler.process
142
+
143
+ expect(Document.last.reference)
144
+ .to eq('X-MAGIC-15')
145
+ end
146
+ end
147
+ end
148
+
149
+ context 'with build_with as block' do
150
+ it_behaves_like 'a request handler', status: :created do
151
+ let(:block) do
152
+ proc do
153
+ documents.where(reference: 'X-MAGIC-20')
154
+ .build(document_params)
155
+ end
156
+ end
157
+
158
+ let(:options_hash) do
159
+ {
160
+ build_with: block
161
+ }
162
+ end
163
+
164
+ let(:extra_params) do
165
+ {
166
+ document: {
167
+ name: 'My Document'
168
+ }
169
+ }
170
+ end
171
+
172
+ let(:expected_json) do
173
+ {
174
+ 'name' => 'My Document',
175
+ 'reference' => 'X-MAGIC-20'
176
+ }
177
+ end
178
+
179
+ it 'creates entry' do
180
+ expect { handler.process }
181
+ .to change(Document, :count)
182
+ .by(1)
183
+ end
184
+
185
+ it 'builds entity with custom method' do
186
+ handler.process
187
+
188
+ expect(Document.last.reference)
189
+ .to eq('X-MAGIC-20')
190
+ end
191
+ end
26
192
  end
27
193
 
28
194
  context 'when payload is invalid' do
@@ -30,6 +30,97 @@ describe Azeroth::RequestHandler::Update do
30
30
  end
31
31
  end
32
32
 
33
+ context 'with before_save block option' do
34
+ it_behaves_like 'a request handler' do
35
+ let(:block) do
36
+ value = 10
37
+ proc do
38
+ document.reference = "X-MAGIC-#{value}"
39
+ end
40
+ end
41
+
42
+ let(:options_hash) do
43
+ {
44
+ before_save: block
45
+ }
46
+ end
47
+
48
+ let(:expected_resource) { document }
49
+
50
+ let(:extra_params) do
51
+ {
52
+ id: document.id,
53
+ document: {
54
+ name: 'New Name'
55
+ }
56
+ }
57
+ end
58
+
59
+ let(:expected_json) do
60
+ {
61
+ 'name' => 'New Name',
62
+ 'reference' => 'X-MAGIC-10'
63
+ }
64
+ end
65
+
66
+ it 'updates the values given by request' do
67
+ expect { handler.process }
68
+ .to change { document.reload.name }
69
+ .from(document.name)
70
+ .to('New Name')
71
+ end
72
+
73
+ it 'updates the values done by before_save' do
74
+ expect { handler.process }
75
+ .to change { document.reload.reference }
76
+ .from(nil)
77
+ .to('X-MAGIC-10')
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'with before_save symbol option' do
83
+ it_behaves_like 'a request handler' do
84
+ let(:options_hash) do
85
+ {
86
+ before_save: :add_magic_reference
87
+ }
88
+ end
89
+
90
+ let(:expected_resource) { document }
91
+
92
+ let(:extra_params) do
93
+ {
94
+ id: document.id,
95
+ document: {
96
+ name: 'New Name'
97
+ }
98
+ }
99
+ end
100
+
101
+ let(:expected_json) do
102
+ {
103
+ 'name' => 'New Name',
104
+ 'reference' => 'X-MAGIC-15'
105
+ }
106
+ end
107
+
108
+ it 'updates the values given by request' do
109
+ expect { handler.process }
110
+ .to change { document.reload.name }
111
+ .from(document.name)
112
+ .to('New Name')
113
+ end
114
+
115
+ it 'updates the values done by before_save' do
116
+ expect { handler.process }
117
+ .to change { document.reload.reference }
118
+ .from(nil)
119
+ .to('X-MAGIC-15')
120
+ end
121
+ end
122
+ end
123
+
33
124
  context 'when payload is invalid' do
34
125
  it_behaves_like 'a request handler',
35
126
  status: :unprocessable_entity do
@@ -4,7 +4,9 @@ require 'spec_helper'
4
4
 
5
5
  describe Azeroth::RequestHandler do
6
6
  describe '#process' do
7
- subject(:handler) { handler_class.new(controller, model) }
7
+ subject(:handler) do
8
+ handler_class.new(controller, model, options)
9
+ end
8
10
 
9
11
  let(:controller) { controller_class.new }
10
12
  let(:params) { ActionController::Parameters.new(parameters) }
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RequestHandlerController < ActionController::Base
4
+ private
5
+
4
6
  def document_params
5
7
  params.require(:document).permit(:name)
6
8
  end
@@ -10,6 +12,15 @@ class RequestHandlerController < ActionController::Base
10
12
  end
11
13
 
12
14
  def document
13
- documents.find(params.require(:id))
15
+ @document ||= documents.find(params.require(:id))
16
+ end
17
+
18
+ def add_magic_reference
19
+ document.reference = 'X-MAGIC-15'
20
+ end
21
+
22
+ def build_magic_document
23
+ documents.where(reference: 'X-MAGIC-15')
24
+ .build(document_params)
14
25
  end
15
26
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :game, class: '::Game' do
5
+ sequence(:name) { |n| "Game #{n}" }
6
+ publisher
7
+ end
8
+ end