brainstem 1.1.1 → 1.3.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.
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