azeroth 0.6.3 → 0.7.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.
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