brainstem 1.1.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -4
  3. data/Gemfile.lock +9 -9
  4. data/README.md +134 -37
  5. data/brainstem.gemspec +1 -1
  6. data/lib/brainstem/api_docs/endpoint.rb +40 -18
  7. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
  8. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
  9. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
  10. data/lib/brainstem/api_docs/presenter.rb +3 -7
  11. data/lib/brainstem/concerns/controller_dsl.rb +138 -14
  12. data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
  13. data/lib/brainstem/dsl/array_block_field.rb +25 -0
  14. data/lib/brainstem/dsl/block_field.rb +69 -0
  15. data/lib/brainstem/dsl/configuration.rb +13 -5
  16. data/lib/brainstem/dsl/field.rb +15 -1
  17. data/lib/brainstem/dsl/fields_block.rb +20 -2
  18. data/lib/brainstem/dsl/hash_block_field.rb +30 -0
  19. data/lib/brainstem/presenter.rb +10 -6
  20. data/lib/brainstem/presenter_validator.rb +20 -11
  21. data/lib/brainstem/version.rb +1 -1
  22. data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
  23. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
  24. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
  25. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
  26. data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
  27. data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
  28. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
  29. data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
  30. data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
  31. data/spec/brainstem/dsl/block_field_spec.rb +188 -0
  32. data/spec/brainstem/dsl/field_spec.rb +86 -20
  33. data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
  34. data/spec/brainstem/presenter_collection_spec.rb +24 -24
  35. data/spec/brainstem/presenter_spec.rb +233 -9
  36. data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
  37. data/spec/spec_helpers/presenters.rb +8 -0
  38. data/spec/spec_helpers/schema.rb +13 -0
  39. metadata +15 -6
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'set'
3
3
  require 'ostruct'
4
+ require 'brainstem/presenter'
4
5
  require 'brainstem/api_docs/presenter'
5
6
 
6
7
  module Brainstem
@@ -111,14 +112,25 @@ module Brainstem
111
112
 
112
113
 
113
114
  describe "#valid_fields" do
114
- let(:field) { Object.new }
115
- before { stub(field).options { { nodoc: nodoc } } }
115
+ let(:presenter_class) do
116
+ Class.new(Brainstem::Presenter) do
117
+ presents Workspace
118
+ end
119
+ end
116
120
 
117
- describe "leafs" do
118
- let(:config) { { fields: { a_field: field } } }
121
+ subject { described_class.new(atlas, target_class: 'Workspace', const: presenter_class) }
122
+
123
+ before do
124
+ stub(atlas).find_by_class(anything) { nil }
125
+ end
119
126
 
127
+ describe "leafs" do
120
128
  context "when nodoc" do
121
- let(:nodoc) { true }
129
+ before do
130
+ presenter_class.fields do
131
+ field :new_field, :string, dynamic: lambda { "new_field value" }, nodoc: true
132
+ end
133
+ end
122
134
 
123
135
  it "rejects the field" do
124
136
  expect(subject.valid_fields.count).to eq 0
@@ -126,18 +138,43 @@ module Brainstem
126
138
  end
127
139
 
128
140
  context "when not nodoc" do
141
+ before do
142
+ presenter_class.fields do
143
+ field :new_field, :string, dynamic: lambda { "new_field value" }, nodoc: false
144
+ field :new_field2, :string, dynamic: lambda { "new_field2 value" }
145
+ end
146
+ end
147
+
129
148
  it "keeps the field" do
130
- expect(subject.valid_fields.count).to eq 1
149
+ expect(subject.valid_fields.keys).to match_array(%w(new_field new_field2))
131
150
  end
132
151
  end
133
152
  end
134
153
 
135
154
  describe "branches" do
136
155
  describe "single nesting" do
137
- let(:config) { { fields: { nesting_one: { a_field: field } } } }
156
+ context "when nested field is nodoc" do
157
+ before do
158
+ presenter_class.fields do
159
+ fields :nested_field, :hash, dynamic: lambda { {} }, nodoc: true do
160
+ field :sub_field, :string, dynamic: lambda { "sub_field value" }
161
+ end
162
+ end
163
+ end
138
164
 
139
- context "when all nodoc" do
140
- let(:nodoc) { true }
165
+ it "rejects the nested field and its sub fields" do
166
+ expect(subject.valid_fields.count).to eq 0
167
+ end
168
+ end
169
+
170
+ context "when all sub fields in a nested field are nodoc" do
171
+ before do
172
+ presenter_class.fields do
173
+ fields :nested_field, :hash, dynamic: lambda { {} }, nodoc: false do
174
+ field :sub_field, :string, dynamic: lambda { "new_field2 value" }, nodoc: true
175
+ end
176
+ end
177
+ end
141
178
 
142
179
  it "rejects the nested field" do
143
180
  expect(subject.valid_fields.count).to eq 0
@@ -145,30 +182,60 @@ module Brainstem
145
182
  end
146
183
 
147
184
  context "when not all nodoc" do
185
+ before do
186
+ presenter_class.fields do
187
+ fields :nested_field, :hash, dynamic: lambda { {} } do
188
+ field :sub_field, :string, dynamic: lambda { "new_field2 value" }
189
+ end
190
+ end
191
+ end
192
+
148
193
  it "keeps the nested field" do
149
- expect(subject.valid_fields.count).to eq 1
194
+ valid_fields = subject.valid_fields.to_h.with_indifferent_access
195
+
196
+ expect(valid_fields.keys).to eq(%w(nested_field))
197
+ expect(valid_fields[:nested_field].keys).to eq(%w(sub_field))
150
198
  end
151
199
  end
152
-
153
200
  end
154
201
 
155
202
  describe "double nesting" do
156
- let(:config) { { fields: { nesting_one: { nesting_two: { a_field: field } } } } }
157
-
158
- context "when all nodoc" do
159
- let(:nodoc) { true }
203
+ context "when the only double nested field is nodoc" do
204
+ before do
205
+ presenter_class.fields do
206
+ fields :nested_field, :hash, dynamic: lambda { {} } do
207
+ fields :double_nested_field, :hash, dynamic: lambda { {} }, nodoc: true do
208
+ field :leaf_field, :string, dynamic: lambda { "leaf value" }
209
+ end
210
+ end
211
+ end
212
+ end
160
213
 
161
- it "rejects the nested field" do
214
+ it "rejects the nested field and its sub fields" do
162
215
  expect(subject.valid_fields.count).to eq 0
163
216
  end
164
217
  end
165
218
 
166
219
  context "when not all nodoc" do
220
+ before do
221
+ presenter_class.fields do
222
+ fields :nested_field, :hash, dynamic: lambda { {} } do
223
+ fields :double_nested_field, :hash, dynamic: lambda { {} } do
224
+ field :leaf_field_1, :string, dynamic: lambda { "leaf_field_1 value" }
225
+ field :leaf_field_2, :string, dynamic: lambda { "leaf_field_2 value" }, nodoc: false
226
+ end
227
+ end
228
+ end
229
+ end
230
+
167
231
  it "keeps the nested field" do
168
- expect(subject.valid_fields.count).to eq 1
232
+ valid_fields = subject.valid_fields.to_h.with_indifferent_access
233
+
234
+ expect(valid_fields.keys).to match_array(%w(nested_field))
235
+ expect(valid_fields[:nested_field].keys).to match_array(%w(double_nested_field))
236
+ expect(valid_fields[:nested_field][:double_nested_field].keys).to match_array(%w(leaf_field_1 leaf_field_2))
169
237
  end
170
238
  end
171
-
172
239
  end
173
240
  end
174
241
  end
@@ -102,23 +102,31 @@ module Brainstem
102
102
  end
103
103
 
104
104
  describe ".model_params" do
105
+ let(:root_proc) { Proc.new {} }
106
+
105
107
  before do
106
108
  stub(subject).brainstem_model_name { "widgets" }
109
+ stub(subject).format_root_name(:widgets) { root_proc }
107
110
  end
108
111
 
109
112
  it "evaluates the block given to it" do
110
- mock(subject).valid(:thing, root: "widgets")
113
+ mock(subject).valid(:thing, :string, 'root' => root_proc, 'ancestors' => [root_proc])
111
114
 
112
115
  subject.model_params :widgets do |param|
113
- param.valid :thing
116
+ param.valid :thing, :string
114
117
  end
115
118
  end
116
119
 
117
120
  it "merges options" do
118
- mock(subject).valid(:thing, root: "widgets", nodoc: true)
121
+ mock(subject).valid(:thing, :integer,
122
+ 'root' => root_proc,
123
+ 'ancestors' => [root_proc],
124
+ 'nodoc' => true,
125
+ 'required' => true
126
+ )
119
127
 
120
128
  subject.model_params :widgets do |param|
121
- param.valid :thing, nodoc: true
129
+ param.valid :thing, :integer, nodoc: true, required: true
122
130
  end
123
131
  end
124
132
  end
@@ -127,29 +135,405 @@ module Brainstem
127
135
  context "when given a name and an options hash" do
128
136
  it "appends to the valid params hash" do
129
137
  subject.brainstem_params do
130
- valid :sprocket_name,
131
- info: "sprockets[sprocket_name] is required"
138
+ valid :sprocket_ids, :array,
139
+ info: "sprockets[sprocket_ids] is required",
140
+ required: true,
141
+ item_type: :integer
132
142
  end
133
143
 
134
- expect(subject.configuration[:_default][:valid_params][:sprocket_name][:info]).to \
135
- eq "sprockets[sprocket_name] is required"
144
+ valid_params = subject.configuration[:_default][:valid_params]
145
+ expect(valid_params.keys.length).to eq(1)
146
+ expect(valid_params.keys[0]).to be_a(Proc)
147
+ expect(valid_params.keys[0].call).to eq("sprocket_ids")
148
+
149
+ sprocket_ids_config = valid_params[valid_params.keys[0]]
150
+ expect(sprocket_ids_config[:info]).to eq "sprockets[sprocket_ids] is required"
151
+ expect(sprocket_ids_config[:required]).to be_truthy
152
+ expect(sprocket_ids_config[:type]).to eq("array")
153
+ expect(sprocket_ids_config[:item_type]).to eq("integer")
136
154
  end
137
155
  end
138
156
 
139
- context "when given a name and an options hash" do
157
+ context "when given a name and an HWIA options hash" do
140
158
  it "appends to the valid params hash" do
141
- # This is HWIA, so all keys are stringified
159
+ # This is Hash With Indifferent Access, so all keys are stringified
142
160
  data = {
143
161
  "recursive" => true,
144
- "info" => "sprockets[sub_sprockets] is recursive and an array"
162
+ "info" => "sprockets[sub_sprockets] is recursive and an array",
163
+ "required" => true
145
164
  }
146
165
 
147
166
  subject.brainstem_params do
148
- valid :sub_sprockets, data
167
+ valid :sub_sprockets, :hash, data
168
+ end
169
+
170
+ valid_params = subject.configuration[:_default][:valid_params]
171
+ expect(valid_params.keys.length).to eq(1)
172
+ expect(valid_params.keys[0].call).to eq("sub_sprockets")
173
+ expect(valid_params[valid_params.keys[0]]).to eq({
174
+ "recursive" => true,
175
+ "info" => "sprockets[sub_sprockets] is recursive and an array",
176
+ "required" => true,
177
+ "type" => "hash",
178
+ "nodoc" => false
179
+ })
180
+ end
181
+ end
182
+
183
+ context "when no options are provided" do
184
+ it "sets default options for the param" do
185
+ subject.brainstem_params do
186
+ valid :sprocket_name, :text
187
+ end
188
+
189
+ valid_params = subject.configuration[:_default][:valid_params]
190
+ expect(valid_params.keys.length).to eq(1)
191
+ expect(valid_params.keys[0].call).to eq("sprocket_name")
192
+
193
+ configuration = valid_params[valid_params.keys[0]]
194
+ expect(configuration[:nodoc]).to be_falsey
195
+ expect(configuration[:required]).to be_falsey
196
+ expect(configuration[:type]).to eq("text")
197
+ end
198
+
199
+ context "when block is specified" do
200
+ it "defaults type to string and sets default options for the param" do
201
+ subject.brainstem_params do
202
+ valid :sprocket, :hash do
203
+ valid :title, :string
204
+ end
205
+ end
206
+
207
+ valid_params = subject.configuration[:_default][:valid_params]
208
+ expect(valid_params.keys.length).to eq(2)
209
+
210
+ param_keys = valid_params.keys
211
+ expect(param_keys[0].call).to eq('sprocket')
212
+ expect(param_keys[1].call).to eq('title')
213
+
214
+ sprocket_key = param_keys[0]
215
+ sprocket_configuration = valid_params[sprocket_key]
216
+ expect(sprocket_configuration[:nodoc]).to be_falsey
217
+ expect(sprocket_configuration[:required]).to be_falsey
218
+ expect(sprocket_configuration[:type]).to eq('hash')
219
+ expect(sprocket_configuration[:ancestors]).to be_nil
220
+ expect(sprocket_configuration[:root]).to be_nil
221
+
222
+ title_configuration = valid_params[param_keys[1]]
223
+ expect(title_configuration[:nodoc]).to be_falsey
224
+ expect(title_configuration[:required]).to be_falsey
225
+ expect(title_configuration[:type]).to eq('string')
226
+ expect(title_configuration[:root]).to be_nil
227
+ expect(title_configuration[:ancestors]).to eq([sprocket_key])
228
+ end
229
+ end
230
+ end
231
+
232
+ context "when type is hash" do
233
+ it "adds the nested fields to valid params" do
234
+ subject.brainstem_params do
235
+ valid :id, :integer
236
+
237
+ valid :info, :hash, required: true do |param|
238
+ param.valid :title, :string, required: true
239
+ end
240
+
241
+ model_params :sprocket do |param|
242
+ param.valid :data, :text
243
+ end
244
+ end
245
+
246
+ valid_params = subject.configuration[:_default][:valid_params]
247
+ expect(valid_params.keys.length).to eq(4)
248
+
249
+ param_keys = valid_params.keys
250
+ expect(param_keys[0].call).to eq('id')
251
+ expect(param_keys[1].call).to eq('info')
252
+ expect(param_keys[2].call).to eq('title')
253
+ expect(param_keys[3].call).to eq('data')
254
+
255
+ id_config = valid_params[param_keys[0]]
256
+ expect(id_config[:root]).to be_nil
257
+ expect(id_config[:ancestors]).to be_nil
258
+
259
+ info_key = param_keys[1]
260
+ info_config = valid_params[info_key]
261
+ expect(info_config[:root]).to be_nil
262
+ expect(info_config[:ancestors]).to be_nil
263
+
264
+ info_title_config = valid_params[param_keys[2]]
265
+ expect(info_title_config[:root]).to be_nil
266
+ expect(info_title_config[:ancestors]).to eq([info_key])
267
+
268
+ sprocket_data_config = valid_params[param_keys[3]]
269
+ sprocket_data_root_key = sprocket_data_config[:root]
270
+ expect(sprocket_data_root_key).to be_present
271
+ expect(sprocket_data_config[:ancestors]).to eq([sprocket_data_root_key])
272
+ end
273
+
274
+ context "when multi nested attributes are specified" do
275
+ it "adds the nested fields to valid params" do
276
+ subject.brainstem_params do
277
+ model_params :sprocket do |param|
278
+ param.valid :title, :string
279
+
280
+ param.valid :details, :hash do |nested_param|
281
+ nested_param.valid :category, :string
282
+
283
+ nested_param.valid :data, :hash do |double_nested_param|
284
+ double_nested_param.valid :raw_text, :string
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ valid_params = subject.configuration[:_default][:valid_params]
291
+ param_keys = valid_params.keys
292
+ expect(param_keys.length).to eq(5)
293
+
294
+ expect(param_keys[0].call).to eq('title')
295
+ title_config = valid_params[param_keys[0]]
296
+ root_param_key = title_config[:root]
297
+ expect(root_param_key).to be_present
298
+ expect(title_config[:ancestors]).to eq([root_param_key])
299
+
300
+ expect(param_keys[1].call).to eq('details')
301
+ details_key = param_keys[1]
302
+ details_config = valid_params[details_key]
303
+ expect(details_config[:root]).to eq(root_param_key)
304
+ expect(details_config[:ancestors]).to eq([root_param_key])
305
+
306
+ expect(param_keys[2].call).to eq('category')
307
+ details_category_config = valid_params[param_keys[2]]
308
+ expect(details_category_config[:root]).to be_nil
309
+ expect(details_category_config[:ancestors]).to eq([root_param_key, details_key])
310
+
311
+ expect(param_keys[3].call).to eq('data')
312
+ details_data_key = param_keys[3]
313
+ details_data_config = valid_params[details_data_key]
314
+ expect(details_data_config[:root]).to be_nil
315
+ expect(details_data_config[:ancestors]).to eq([root_param_key, details_key])
316
+
317
+ expect(param_keys[4].call).to eq('raw_text')
318
+ details_data_raw_text_config = valid_params[param_keys[4]]
319
+ expect(details_data_raw_text_config[:root]).to be_nil
320
+ expect(details_data_raw_text_config[:ancestors]).to eq([root_param_key, details_key, details_data_key])
321
+ end
322
+ end
323
+
324
+ context "when root has no required attribute" do
325
+ it "sets the required attribute for the parent configuration to false" do
326
+ subject.brainstem_params do
327
+ valid :template, :hash do |param|
328
+ param.valid :id, :integer
329
+ param.valid :title, :string
330
+ end
331
+ end
332
+
333
+ valid_params = subject.configuration[:_default][:valid_params]
334
+
335
+ template_key = valid_params.keys[0]
336
+ expect(template_key.call).to eq('template')
337
+ expect(valid_params[template_key][:required]).to be_falsey
338
+ end
339
+
340
+ context "when one of the nested fields is required" do
341
+ it "sets the required attribute for the parent configuration to true" do
342
+ subject.brainstem_params do
343
+ valid :template, :hash do |param|
344
+ param.valid :id, :integer, required: true
345
+ param.valid :title, :string
346
+ end
347
+
348
+ model_params :sprocket do |param|
349
+ param.valid :details, :hash do |nested_param|
350
+ nested_param.valid :data, :hash do |double_nested_param|
351
+ double_nested_param.valid :raw_text, :string, required: true
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ valid_params = subject.configuration[:_default][:valid_params]
358
+
359
+ template_key = valid_params.keys[0]
360
+ expect(template_key.call).to eq('template')
361
+ expect(valid_params[template_key][:required]).to be_truthy
362
+
363
+ sprocket_details_key = valid_params.keys[3]
364
+ expect(sprocket_details_key.call).to eq('details')
365
+ expect(valid_params[sprocket_details_key][:required]).to be_truthy
366
+
367
+ sprocket_details_data_key = valid_params.keys[4]
368
+ expect(sprocket_details_data_key.call).to eq('data')
369
+ expect(valid_params[sprocket_details_data_key][:required]).to be_truthy
370
+
371
+ sprocket_details_data_raw_text_key = valid_params.keys[5]
372
+ expect(sprocket_details_data_raw_text_key.call).to eq('raw_text')
373
+ expect(valid_params[sprocket_details_data_raw_text_key][:required]).to be_truthy
374
+ end
375
+ end
376
+ end
377
+
378
+ context "when root is nodoc" do
379
+ it "updates the nodoc property on its nested fields to true" do
380
+ subject.brainstem_params do
381
+ model_params :sprocket do |param|
382
+ param.valid :title, :string
383
+ param.valid :details, :hash, nodoc: true do |param|
384
+ param.valid :category, :string
385
+ param.valid :data, :hash do |nested_param|
386
+ param.valid :raw_text, :string
387
+ end
388
+ end
389
+ end
390
+ end
391
+
392
+ valid_params = subject.configuration[:_default][:valid_params]
393
+
394
+ title_key = valid_params.keys[0]
395
+ expect(title_key.call).to eq('title')
396
+ expect(valid_params[title_key][:nodoc]).to be_falsey
397
+
398
+ details_key = valid_params.keys[1]
399
+ expect(details_key.call).to eq('details')
400
+ expect(valid_params[details_key][:nodoc]).to be_truthy
401
+
402
+ details_category_key = valid_params.keys[2]
403
+ expect(details_category_key.call).to eq('category')
404
+ expect(valid_params[details_category_key][:nodoc]).to be_truthy
405
+
406
+ details_data_key = valid_params.keys[3]
407
+ expect(details_data_key.call).to eq('data')
408
+ expect(valid_params[details_data_key][:nodoc]).to be_truthy
409
+
410
+ details_data_raw_text_key = valid_params.keys[4]
411
+ expect(details_data_raw_text_key.call).to eq('raw_text')
412
+ expect(valid_params[details_data_raw_text_key][:nodoc]).to be_truthy
413
+ end
414
+ end
415
+ end
416
+
417
+ context "when type is array" do
418
+ it "sets the type and sub type appropriately" do
419
+ subject.brainstem_params do
420
+ valid :sprocket_ids, :array,
421
+ required: true,
422
+ item_type: :string
423
+ end
424
+
425
+ valid_params = subject.configuration[:_default][:valid_params]
426
+
427
+ sprocket_ids_key = valid_params.keys[0]
428
+ expect(sprocket_ids_key.call).to eq('sprocket_ids')
429
+
430
+ sprocket_ids_config = valid_params[sprocket_ids_key]
431
+ expect(sprocket_ids_config[:required]).to be_truthy
432
+ expect(sprocket_ids_config[:type]).to eq('array')
433
+ expect(sprocket_ids_config[:item_type]).to eq('string')
434
+ end
435
+
436
+ context "when a block is given" do
437
+ it "sets the type and sub type appropriately" do
438
+ subject.brainstem_params do
439
+ valid :sprocket_tasks, :array, required: true, item_type: 'hash' do |param|
440
+ param.valid :task_id, :integer, required: true
441
+ param.valid :task_title, :string
442
+ end
443
+ end
444
+
445
+ valid_params = subject.configuration[:_default][:valid_params]
446
+
447
+ sprocket_tasks_key = valid_params.keys[0]
448
+ expect(sprocket_tasks_key.call).to eq('sprocket_tasks')
449
+
450
+ sprocket_tasks_config = valid_params[sprocket_tasks_key]
451
+ expect(sprocket_tasks_config[:required]).to be_truthy
452
+ expect(sprocket_tasks_config[:type]).to eq('array')
453
+ expect(sprocket_tasks_config[:item_type]).to eq('hash')
454
+
455
+ task_id_key = valid_params.keys[1]
456
+ expect(task_id_key.call).to eq('task_id')
457
+
458
+ task_id_config = valid_params[task_id_key]
459
+ expect(task_id_config[:required]).to be_truthy
460
+ expect(task_id_config[:type]).to eq('integer')
461
+ expect(task_id_config[:root]).to be_nil
462
+ expect(task_id_config[:ancestors]).to eq([sprocket_tasks_key])
463
+
464
+ task_title_key = valid_params.keys[2]
465
+ expect(task_title_key.call).to eq('task_title')
466
+
467
+ task_title_config = valid_params[task_title_key]
468
+ expect(task_title_config[:required]).to be_falsey
469
+ expect(task_title_config[:type]).to eq('string')
470
+ expect(task_title_config[:root]).to be_nil
471
+ expect(task_title_config[:ancestors]).to eq([sprocket_tasks_key])
472
+ end
473
+ end
474
+ end
475
+
476
+ context "deprecated type behavior" do
477
+ context "when no type is provided" do
478
+ before do
479
+ mock(subject).deprecated_type_warning
480
+ end
481
+
482
+ it "defaults to type string" do
483
+ subject.brainstem_params do
484
+ valid :sprocket_name, required: true
485
+ end
486
+
487
+ valid_params = subject.configuration[:_default][:valid_params]
488
+ expect(valid_params.keys.length).to eq(1)
489
+ expect(valid_params.keys[0].call).to eq("sprocket_name")
490
+
491
+ configuration = valid_params[valid_params.keys[0]]
492
+ expect(configuration[:type]).to eq("string")
493
+ expect(configuration[:required]).to be_truthy
494
+ end
495
+ end
496
+
497
+ context "when no type and options are provided" do
498
+ before do
499
+ mock(subject).deprecated_type_warning
500
+ end
501
+
502
+ it "defaults type to string and sets default options for the param" do
503
+ subject.brainstem_params do
504
+ valid :sprocket_name
505
+ end
506
+
507
+ valid_params = subject.configuration[:_default][:valid_params]
508
+ expect(valid_params.keys.length).to eq(1)
509
+ expect(valid_params.keys[0].call).to eq("sprocket_name")
510
+
511
+ configuration = valid_params[valid_params.keys[0]]
512
+ expect(configuration[:nodoc]).to be_falsey
513
+ expect(configuration[:required]).to be_falsey
514
+ expect(configuration[:type]).to eq("string")
515
+ end
516
+ end
517
+
518
+ context "when type and options are hashes" do
519
+ before do
520
+ mock(subject).deprecated_type_warning
521
+ end
522
+
523
+ it "ignores the type and defaults to string" do
524
+ subject.brainstem_params do
525
+ valid :sprocket_name, { troll: true }, { required: true }
149
526
  end
150
527
 
151
- expect(subject.configuration[:_default][:valid_params][:sub_sprockets]).to \
152
- eq data
528
+ valid_params = subject.configuration[:_default][:valid_params]
529
+ expect(valid_params.keys.length).to eq(1)
530
+ expect(valid_params.keys[0].call).to eq("sprocket_name")
531
+
532
+ configuration = valid_params[valid_params.keys[0]]
533
+ expect(configuration[:nodoc]).to be_falsey
534
+ expect(configuration[:required]).to be_truthy
535
+ expect(configuration[:type]).to eq("string")
536
+ end
153
537
  end
154
538
  end
155
539
  end
@@ -276,15 +660,15 @@ module Brainstem
276
660
  it "uses the existing context" do
277
661
  subject.brainstem_params do
278
662
  action_context :show do
279
- valid :param_1, info: "something"
663
+ valid :param_1, :integer, info: "something"
280
664
  end
281
665
 
282
666
  action_context :show do
283
- valid :param_2, info: "something else"
667
+ valid :param_2, :string, info: "something else"
284
668
  end
285
669
  end
286
670
 
287
- expect(subject.configuration[:show][:valid_params].keys).to \
671
+ expect(subject.configuration[:show][:valid_params].keys.map(&:call)).to \
288
672
  eq ["param_1", "param_2"]
289
673
 
290
674
  end
@@ -297,7 +681,7 @@ module Brainstem
297
681
 
298
682
  subject.brainstem_params do
299
683
  actions [:show, :index] do
300
- valid :param_1, info: "something"
684
+ valid :param_1, :integer, info: "something"
301
685
  end
302
686
  end
303
687
  end
@@ -305,12 +689,12 @@ module Brainstem
305
689
  it "allows passing an array" do
306
690
  subject.brainstem_params do
307
691
  actions [:show, :index] do
308
- valid :param_1, info: "something"
692
+ valid :param_1, :string, info: "something"
309
693
  end
310
694
  end
311
695
 
312
696
  %w(index show).each do |meth|
313
- expect(subject.configuration[meth.to_sym][:valid_params].keys).to \
697
+ expect(subject.configuration[meth.to_sym][:valid_params].keys.map(&:call)).to \
314
698
  include "param_1"
315
699
  end
316
700
  end
@@ -318,17 +702,201 @@ module Brainstem
318
702
  it "allows passing multiple symbols" do
319
703
  subject.brainstem_params do
320
704
  actions :show, :index do
321
- valid :param_1, "something"
705
+ valid :param_1, :integer, info: "something"
322
706
  end
323
707
  end
324
708
 
325
709
  %w(index show).each do |meth|
326
- expect(subject.configuration[meth.to_sym][:valid_params].keys).to \
710
+ expect(subject.configuration[meth.to_sym][:valid_params].keys.map(&:call)).to \
327
711
  include "param_1"
328
712
  end
329
713
  end
330
714
  end
331
715
 
716
+ describe "#valid_params_tree" do
717
+ context "when no root is specified" do
718
+ it "returns the field names as the top level keys" do
719
+ stub.any_instance_of(subject).action_name { "show" }
720
+
721
+ subject.brainstem_params do
722
+ actions :show do
723
+ valid :sprocket_ids, :array,
724
+ info: "sprockets[sprocket_ids] is required",
725
+ required: true,
726
+ item_type: :integer
727
+
728
+ valid :widget_id, :integer,
729
+ info: "sprockets[widget_id] is not required"
730
+ end
731
+ end
732
+
733
+ result = subject.new.valid_params_tree
734
+ expect(result.keys).to match_array(%w(sprocket_ids widget_id))
735
+
736
+ sprocket_ids_config = result[:sprocket_ids][:_config]
737
+ expect(sprocket_ids_config).to eq({
738
+ "info" => "sprockets[sprocket_ids] is required",
739
+ "required" => true,
740
+ "item_type" => "integer",
741
+ "nodoc" => false,
742
+ "type" => "array"
743
+ })
744
+
745
+ widget_id_config = result[:widget_id][:_config]
746
+ expect(widget_id_config).to eq({
747
+ "info" => "sprockets[widget_id] is not required",
748
+ "required" => false,
749
+ "nodoc" => false,
750
+ "type" => "integer"
751
+ })
752
+ end
753
+ end
754
+
755
+ context "when root is specified" do
756
+ let(:brainstem_model_name) { "widget" }
757
+
758
+ it "returns the root as a top level key" do
759
+ stub(subject).brainstem_model_name { brainstem_model_name }
760
+ stub.any_instance_of(subject).brainstem_model_name { brainstem_model_name }
761
+ stub.any_instance_of(subject).action_name { "show" }
762
+
763
+ subject.brainstem_params do
764
+ valid :unrelated_root_key, :string,
765
+ info: "it's unrelated.",
766
+ required: true
767
+
768
+ model_params(brainstem_model_name) do |params|
769
+ params.valid :sprocket_parent_id, :long,
770
+ info: "widget[sprocket_parent_id] is not required"
771
+ end
772
+
773
+ actions :show do
774
+ model_params(brainstem_model_name) do |params|
775
+ params.valid :sprocket_name, :string,
776
+ info: "widget[sprocket_name] is required",
777
+ required: true
778
+ end
779
+ end
780
+ end
781
+
782
+ result = subject.new.valid_params_tree
783
+ expect(result.keys).to match_array(%w(widget unrelated_root_key))
784
+ expect(result[:widget].keys).to match_array(%w(sprocket_parent_id sprocket_name))
785
+
786
+ sprocket_parent_id_config = result[:widget][:sprocket_parent_id][:_config]
787
+ expect(sprocket_parent_id_config).to eq({
788
+ "info" => "widget[sprocket_parent_id] is not required",
789
+ "required" => false,
790
+ "nodoc" => false,
791
+ "type" => "long"
792
+ })
793
+
794
+ sprocket_name_config = result[:widget][:sprocket_name][:_config]
795
+ expect(sprocket_name_config).to eq({
796
+ "info" => "widget[sprocket_name] is required",
797
+ "required" => true,
798
+ "nodoc" => false,
799
+ "type" => "string"
800
+ })
801
+ end
802
+ end
803
+
804
+ context "when multiple fields share the same name" do
805
+ let(:brainstem_model_name) { "widget" }
806
+
807
+ it "retains config for both fields" do
808
+ stub(subject).brainstem_model_name { brainstem_model_name }
809
+ stub.any_instance_of(subject).brainstem_model_name { brainstem_model_name }
810
+ stub.any_instance_of(subject).action_name { "update" }
811
+
812
+ subject.brainstem_params do
813
+ actions :update do
814
+ valid :id, :integer,
815
+ info: "ID of the widget.",
816
+ required: true
817
+
818
+ model_params(brainstem_model_name) do |params|
819
+ params.valid :id, :integer,
820
+ info: "widget[id] is optional"
821
+ end
822
+ end
823
+ end
824
+
825
+ result = subject.new.valid_params_tree
826
+ expect(result.keys).to match_array(%w(widget id))
827
+ expect(result[:widget].keys).to match_array(%w(id))
828
+
829
+ id_param_config = result[:id][:_config]
830
+ expect(id_param_config).to eq({
831
+ "info" => "ID of the widget.",
832
+ "required" => true,
833
+ "nodoc" => false,
834
+ "type" => "integer"
835
+ })
836
+
837
+ nested_id_param_config = result[:widget][:id][:_config]
838
+ expect(nested_id_param_config).to eq({
839
+ "info" => "widget[id] is optional",
840
+ "required" => false,
841
+ "nodoc" => false,
842
+ "type" => "integer"
843
+ })
844
+ end
845
+ end
846
+
847
+ context "when multiple nested params are specified" do
848
+ let(:brainstem_model_name) { "widget" }
849
+
850
+ it "retains config for both fields" do
851
+ stub(subject).brainstem_model_name { brainstem_model_name }
852
+ stub.any_instance_of(subject).brainstem_model_name { brainstem_model_name }
853
+ stub.any_instance_of(subject).action_name { "update" }
854
+
855
+ subject.brainstem_params do
856
+ actions :update do
857
+ model_params(brainstem_model_name) do |params|
858
+ params.valid :title, :string,
859
+ info: "widget[title] is required",
860
+ required: true
861
+
862
+ params.valid :sprocket, :hash do |nested_params|
863
+ nested_params.valid :name, :string,
864
+ info: "sprocket[name] is optional"
865
+ end
866
+ end
867
+ end
868
+ end
869
+
870
+ result = subject.new.valid_params_tree
871
+ expect(result.keys).to match_array(%w(widget))
872
+ expect(result[:widget].keys).to match_array(%w(title sprocket))
873
+
874
+ title_param_config = result[:widget][:title][:_config]
875
+ expect(title_param_config).to eq({
876
+ "info" => "widget[title] is required",
877
+ "required" => true,
878
+ "nodoc" => false,
879
+ "type" => "string"
880
+ })
881
+
882
+ sprocket_param_config = result[:widget][:sprocket][:_config]
883
+ expect(sprocket_param_config).to eq({
884
+ "required" => false,
885
+ "nodoc" => false,
886
+ "type" => "hash"
887
+ })
888
+
889
+ sprocket_name_param_config = result[:widget][:sprocket][:name][:_config]
890
+ expect(sprocket_name_param_config).to eq({
891
+ "info" => "sprocket[name] is optional",
892
+ "required" => false,
893
+ "nodoc" => false,
894
+ "type" => "string"
895
+ })
896
+ end
897
+ end
898
+ end
899
+
332
900
  describe "#brainstem_valid_params" do
333
901
  let(:brainstem_model_name) { "widget" }
334
902
 
@@ -337,18 +905,20 @@ module Brainstem
337
905
  stub.any_instance_of(subject).brainstem_model_name { brainstem_model_name }
338
906
 
339
907
  subject.brainstem_params do
340
- valid :unrelated_root_key,
341
- info: "it's unrelated."
908
+ valid :unrelated_root_key, :string,
909
+ info: "it's unrelated.",
910
+ required: true
342
911
 
343
912
  model_params(brainstem_model_name) do |params|
344
- params.valid :sprocket_parent_id,
345
- info: "sprockets[sprocket_parent_id] is required"
913
+ params.valid :sprocket_parent_id, :long,
914
+ info: "sprockets[sprocket_parent_id] is not required"
346
915
  end
347
916
 
348
917
  actions :show do
349
918
  model_params(brainstem_model_name) do |params|
350
- params.valid :sprocket_name,
351
- info: "sprockets[sprocket_name] is required"
919
+ params.valid :sprocket_name, :string,
920
+ info: "sprockets[sprocket_name] is required",
921
+ required: true
352
922
  end
353
923
  end
354
924
  end
@@ -359,7 +929,7 @@ module Brainstem
359
929
 
360
930
  subject.brainstem_params do
361
931
  model_params Proc.new { |k| k.arbitrary_method } do |params|
362
- params.valid :nested_key, info: "it's nested!"
932
+ params.valid :nested_key, :hash, info: "it's nested!"
363
933
  end
364
934
  end
365
935
 
@@ -371,8 +941,22 @@ module Brainstem
371
941
  stub.any_instance_of(subject).action_name { "show" }
372
942
 
373
943
  expect(subject.new.brainstem_valid_params).to eq({
374
- "sprocket_name" => { "info" => "sprockets[sprocket_name] is required", "root" => "widget" },
375
- "sprocket_parent_id" => { "info" => "sprockets[sprocket_parent_id] is required", "root" => "widget" }
944
+ "sprocket_name" => {
945
+ "_config" => {
946
+ "info" => "sprockets[sprocket_name] is required",
947
+ "required" => true,
948
+ "type" => "string",
949
+ "nodoc" => false
950
+ }
951
+ },
952
+ "sprocket_parent_id" => {
953
+ "_config" => {
954
+ "info" => "sprockets[sprocket_parent_id] is not required",
955
+ "type" => "long",
956
+ "nodoc" => false,
957
+ "required" => false
958
+ }
959
+ }
376
960
  })
377
961
  end
378
962