brainstem 1.1.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -4
- data/Gemfile.lock +9 -9
- data/README.md +134 -37
- data/brainstem.gemspec +1 -1
- data/lib/brainstem/api_docs/endpoint.rb +40 -18
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
- data/lib/brainstem/api_docs/presenter.rb +3 -7
- data/lib/brainstem/concerns/controller_dsl.rb +138 -14
- data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
- data/lib/brainstem/dsl/array_block_field.rb +25 -0
- data/lib/brainstem/dsl/block_field.rb +69 -0
- data/lib/brainstem/dsl/configuration.rb +13 -5
- data/lib/brainstem/dsl/field.rb +15 -1
- data/lib/brainstem/dsl/fields_block.rb +20 -2
- data/lib/brainstem/dsl/hash_block_field.rb +30 -0
- data/lib/brainstem/presenter.rb +10 -6
- data/lib/brainstem/presenter_validator.rb +20 -11
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
- data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
- data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
- data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
- data/spec/brainstem/dsl/block_field_spec.rb +188 -0
- data/spec/brainstem/dsl/field_spec.rb +86 -20
- data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
- data/spec/brainstem/presenter_collection_spec.rb +24 -24
- data/spec/brainstem/presenter_spec.rb +233 -9
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
- data/spec/spec_helpers/presenters.rb +8 -0
- data/spec/spec_helpers/schema.rb +13 -0
- metadata +15 -6
@@ -69,7 +69,6 @@ describe Brainstem::Presenter do
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
|
73
72
|
describe ".possible_brainstem_keys" do
|
74
73
|
let(:presented_class) { Class.new }
|
75
74
|
let(:other_presented_class) { Class.new }
|
@@ -301,6 +300,231 @@ describe Brainstem::Presenter do
|
|
301
300
|
end
|
302
301
|
end
|
303
302
|
end
|
303
|
+
|
304
|
+
describe 'handling nested hash block fields' do
|
305
|
+
let(:presenter_class) do
|
306
|
+
Class.new(Brainstem::Presenter) do
|
307
|
+
presents Workspace
|
308
|
+
|
309
|
+
helper do
|
310
|
+
def current_user
|
311
|
+
'jane'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
conditionals do
|
316
|
+
request :user_is_bob, lambda { current_user == 'bob' }, info: 'visible only to bob'
|
317
|
+
end
|
318
|
+
|
319
|
+
fields do
|
320
|
+
fields :participant, :hash, via: :lead_user, if: :user_is_bob do
|
321
|
+
field :username, :string
|
322
|
+
end
|
323
|
+
|
324
|
+
fields :lead_user do
|
325
|
+
field :username, :string, dynamic: lambda { |workspace| workspace.lead_user.username }
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
let(:presenter) { presenter_class.new }
|
331
|
+
|
332
|
+
before do
|
333
|
+
stub.any_instance_of(Brainstem::DSL::Field).presentable?(model, anything) { presentable }
|
334
|
+
end
|
335
|
+
|
336
|
+
context 'when field is presentable' do
|
337
|
+
let(:presentable) { true }
|
338
|
+
|
339
|
+
it 'includes the executable hash block field' do
|
340
|
+
presented_workspace = presenter.group_present([model], []).first
|
341
|
+
|
342
|
+
expect(presented_workspace.keys).to include('participant')
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'includes the non executable hash block field' do
|
346
|
+
presented_workspace = presenter.group_present([model], []).first
|
347
|
+
|
348
|
+
expect(presented_workspace.keys).to include('lead_user')
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'when field is not presentable' do
|
353
|
+
let(:presentable) { false }
|
354
|
+
|
355
|
+
it 'does not include executable hash block fields' do
|
356
|
+
presented_workspace = presenter.group_present([model], []).first
|
357
|
+
|
358
|
+
expect(presented_workspace.keys).to_not include('participant')
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'always includes non-executable hash block fields' do
|
362
|
+
presented_workspace = presenter.group_present([model], []).first
|
363
|
+
|
364
|
+
expect(presented_workspace.keys).to include('lead_user')
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
describe 'handling of nested array fields' do
|
370
|
+
let(:array_values) { [OpenStruct.new(name: 1), OpenStruct.new(name: 2)] }
|
371
|
+
let(:presenter_class) do
|
372
|
+
Class.new(Brainstem::Presenter) do
|
373
|
+
presents Workspace
|
374
|
+
|
375
|
+
fields do
|
376
|
+
fields :participants, :array, via: :members do
|
377
|
+
field :username, :string
|
378
|
+
end
|
379
|
+
|
380
|
+
fields :accessible_by, :array, dynamic: -> { [OpenStruct.new(name: 1), OpenStruct.new(name: 2)] } do
|
381
|
+
field :name, :string
|
382
|
+
field :full_name, :string, via: :name
|
383
|
+
field :formatted_name, :string, dynamic: -> (model) { model.name * 2 }
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
let(:presenter) { presenter_class.new }
|
389
|
+
|
390
|
+
context 'when dynamic option is specified' do
|
391
|
+
def extract_values(fields, field_name, nested_field_name)
|
392
|
+
fields[field_name].map { |data| data[nested_field_name] }
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'calls named methods' do
|
396
|
+
fields = presenter.group_present([model]).first
|
397
|
+
|
398
|
+
expect(fields).to have_key('accessible_by')
|
399
|
+
expect(extract_values(fields, 'accessible_by', 'name')).to eq([1, 2])
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'can call methods with :via' do
|
403
|
+
fields = presenter.group_present([model]).first
|
404
|
+
|
405
|
+
expect(fields).to have_key('accessible_by')
|
406
|
+
expect(extract_values(fields, 'accessible_by', 'full_name')).to eq([1, 2])
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'can call a dynamic lambda' do
|
410
|
+
fields = presenter.group_present([model]).first
|
411
|
+
|
412
|
+
expect(fields).to have_key('accessible_by')
|
413
|
+
expect(extract_values(fields, 'accessible_by', 'formatted_name')).to eq([2, 4])
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
context 'when via option is specified' do
|
418
|
+
let(:participants) { [model.user] }
|
419
|
+
let(:presented_field_data) { participants.map { |user| { 'username' => user.username } } }
|
420
|
+
|
421
|
+
it 'returns an array of hashes with the specified field' do
|
422
|
+
fields = presenter.group_present([model]).first
|
423
|
+
|
424
|
+
expect(fields).to have_key('participants')
|
425
|
+
expect(fields['participants']).to eq(presented_field_data)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe 'when nested fields specify not using the parent value' do
|
430
|
+
let(:presenter_class) do
|
431
|
+
Class.new(Brainstem::Presenter) do
|
432
|
+
presents Workspace
|
433
|
+
|
434
|
+
fields do
|
435
|
+
fields :tasks, :array, via: :tasks do
|
436
|
+
field :name, :string
|
437
|
+
field :secret, :string,
|
438
|
+
info: 'a secret, via secret_info',
|
439
|
+
via: :secret_info,
|
440
|
+
use_parent_value: false
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
let(:tasks) { Task.where(workspace_id: model.id).order(:id).to_a }
|
446
|
+
let(:presented_field_data) { tasks.map { |task| { 'name' => task.name, 'secret' => model.secret_info } } }
|
447
|
+
|
448
|
+
it 'returns an array of hashes while using the correct model to evaluate the properties' do
|
449
|
+
fields = presenter.group_present([model]).first
|
450
|
+
|
451
|
+
expect(fields).to have_key('tasks')
|
452
|
+
expect(fields['tasks']).to eq(presented_field_data)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
describe 'handling of conditional fields' do
|
457
|
+
let(:presenter_class) do
|
458
|
+
Class.new(Brainstem::Presenter) do
|
459
|
+
presents Workspace
|
460
|
+
|
461
|
+
helper do
|
462
|
+
def current_user
|
463
|
+
'jane'
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
conditionals do
|
468
|
+
model :title_is_hello, lambda { |model| model.title == 'hello' }, info: 'visible when the title is hello'
|
469
|
+
request :user_is_bob, lambda { current_user == 'bob' }, info: 'visible only to bob'
|
470
|
+
end
|
471
|
+
|
472
|
+
fields do
|
473
|
+
with_options if: :user_is_bob do
|
474
|
+
fields :tasks, :array, dynamic: lambda { |workspace| workspace.tasks.to_a } do
|
475
|
+
field :name, :string
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
fields :members, :array, via: :members, if: :title_is_hello do
|
480
|
+
field :hello_title, :string,
|
481
|
+
info: 'the title, when hello',
|
482
|
+
dynamic: lambda { 'title is hello' }
|
483
|
+
|
484
|
+
field :foo, :string,
|
485
|
+
info: 'a secret, via secret_info',
|
486
|
+
dynamic: lambda { 'foo' },
|
487
|
+
if: [:user_is_bob]
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'does not return conditional fields when their :if conditionals do not match' do
|
494
|
+
fields = presenter.group_present([model]).first
|
495
|
+
|
496
|
+
expect(fields).to_not have_key('tasks')
|
497
|
+
expect(fields).to_not have_key('members')
|
498
|
+
end
|
499
|
+
|
500
|
+
it 'returns conditional fields when their :if matches' do
|
501
|
+
model.title = 'hello'
|
502
|
+
|
503
|
+
fields = presenter.group_present([model]).first
|
504
|
+
|
505
|
+
expect(fields).to_not have_key('tasks')
|
506
|
+
expect(fields).to have_key('members')
|
507
|
+
expect(fields['members'][0]).to_not have_key('foo')
|
508
|
+
expect(fields['members'][0]['hello_title']).to eq 'title is hello'
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'returns fields with the :if option only when all of the conditionals in that :if are true' do
|
512
|
+
model.title = 'hello'
|
513
|
+
presenter.class.helper do
|
514
|
+
def current_user
|
515
|
+
'bob'
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
fields = presenter.group_present([model]).first
|
520
|
+
|
521
|
+
expect(fields).to have_key('tasks')
|
522
|
+
expect(fields).to have_key('members')
|
523
|
+
expect(fields['members'][0]['foo']).to eq('foo')
|
524
|
+
expect(fields['members'][0]['hello_title']).to eq('title is hello')
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
304
528
|
end
|
305
529
|
|
306
530
|
describe "adding object ids as strings" do
|
@@ -650,8 +874,8 @@ describe Brainstem::Presenter do
|
|
650
874
|
let(:presenter) { presenter_class.new }
|
651
875
|
|
652
876
|
it 'returns only known filters' do
|
653
|
-
presenter_class.filter :owned_by
|
654
|
-
presenter_class.filter(:bar) { |scope| scope }
|
877
|
+
presenter_class.filter :owned_by, :integer
|
878
|
+
presenter_class.filter(:bar, :string) { |scope| scope }
|
655
879
|
expect(presenter.extract_filters({ 'foo' => 'hi' })).to eq({})
|
656
880
|
expect(presenter.extract_filters({ 'owned_by' => '2' })).to eq({ 'owned_by' => '2' })
|
657
881
|
expect(presenter.extract_filters({ 'owned_by' => [2] })).to eq({ 'owned_by' => [2] })
|
@@ -659,7 +883,7 @@ describe Brainstem::Presenter do
|
|
659
883
|
end
|
660
884
|
|
661
885
|
it "converts 'true' and 'false' into true and false" do
|
662
|
-
presenter_class.filter :owned_by
|
886
|
+
presenter_class.filter :owned_by, :boolean
|
663
887
|
expect(presenter.extract_filters({ 'owned_by' => 'true' })).to eq({ 'owned_by' => true })
|
664
888
|
expect(presenter.extract_filters({ 'owned_by' => 'TRUE' })).to eq({ 'owned_by' => true })
|
665
889
|
expect(presenter.extract_filters({ 'owned_by' => 'false' })).to eq({ 'owned_by' => false })
|
@@ -668,19 +892,19 @@ describe Brainstem::Presenter do
|
|
668
892
|
end
|
669
893
|
|
670
894
|
it 'defaults to applying default filters' do
|
671
|
-
presenter_class.filter :owned_by, default: '2'
|
895
|
+
presenter_class.filter :owned_by, :integer, default: '2'
|
672
896
|
expect(presenter.extract_filters({ 'owned_by' => '3' })).to eq({ 'owned_by' => '3' })
|
673
897
|
expect(presenter.extract_filters({})).to eq({ 'owned_by' => '2' })
|
674
898
|
end
|
675
899
|
|
676
900
|
it 'will skip default filters when asked' do
|
677
|
-
presenter_class.filter :owned_by, default: '2'
|
901
|
+
presenter_class.filter :owned_by, :integer, default: '2'
|
678
902
|
expect(presenter.extract_filters({ 'owned_by' => '3' }, apply_default_filters: false)).to eq({ 'owned_by' => '3' })
|
679
903
|
expect(presenter.extract_filters({}, apply_default_filters: false)).to eq({})
|
680
904
|
end
|
681
905
|
|
682
906
|
it 'ignores nil and blank values' do
|
683
|
-
presenter_class.filter :owned_by
|
907
|
+
presenter_class.filter :owned_by, :integer
|
684
908
|
expect(presenter.extract_filters({ 'owned_by' => nil })).to eq({})
|
685
909
|
expect(presenter.extract_filters({ 'owned_by' => '' })).to eq({})
|
686
910
|
end
|
@@ -694,8 +918,8 @@ describe Brainstem::Presenter do
|
|
694
918
|
let(:options) { { apply_default_filters: true } }
|
695
919
|
|
696
920
|
before do
|
697
|
-
presenter_class.filter :owned_by, default: '2'
|
698
|
-
presenter_class.filter(:bar) { |scope| scope.where(id: 6) }
|
921
|
+
presenter_class.filter :owned_by, :integer, default: '2'
|
922
|
+
presenter_class.filter(:bar, :string) { |scope| scope.where(id: 6) }
|
699
923
|
mock(presenter).extract_filters(params, options) { { 'bar' => 'foo', 'owned_by' => '2' } }
|
700
924
|
end
|
701
925
|
|
@@ -35,7 +35,7 @@ describe Brainstem::QueryStrategies::FilterAndSearch do
|
|
35
35
|
[search_results, search_results.count]
|
36
36
|
end
|
37
37
|
|
38
|
-
CheesePresenter.filter(:owned_by) { |scope, user_id| scope.owned_by(user_id.to_i) }
|
38
|
+
CheesePresenter.filter(:owned_by, :integer) { |scope, user_id| scope.owned_by(user_id.to_i) }
|
39
39
|
CheesePresenter.sort_order(:id) { |scope, direction| scope.order("cheeses.id #{direction}") }
|
40
40
|
end
|
41
41
|
|
@@ -32,6 +32,14 @@
|
|
32
32
|
field :access_level, :integer, dynamic: lambda { 2 }
|
33
33
|
end
|
34
34
|
|
35
|
+
fields :members, :array, via: :members do
|
36
|
+
field :username, :string
|
37
|
+
field :secret, :string,
|
38
|
+
info: 'a secret, via secret_info',
|
39
|
+
via: :secret_info,
|
40
|
+
use_parent_value: false
|
41
|
+
end
|
42
|
+
|
35
43
|
field :hello_title, :string,
|
36
44
|
info: 'the title, when hello',
|
37
45
|
dynamic: lambda { 'title is hello' },
|
data/spec/spec_helpers/schema.rb
CHANGED
@@ -51,10 +51,15 @@ end
|
|
51
51
|
|
52
52
|
class User < ActiveRecord::Base
|
53
53
|
has_many :workspaces
|
54
|
+
|
55
|
+
def type
|
56
|
+
self.class.name
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
class Task < ActiveRecord::Base
|
57
61
|
belongs_to :workspace
|
62
|
+
belongs_to :parent, :class_name => "Task"
|
58
63
|
has_many :sub_tasks, :foreign_key => :parent_id, :class_name => "Task"
|
59
64
|
has_many :posts
|
60
65
|
|
@@ -75,6 +80,10 @@ class Workspace < ActiveRecord::Base
|
|
75
80
|
"this is secret!"
|
76
81
|
end
|
77
82
|
|
83
|
+
def members
|
84
|
+
[user]
|
85
|
+
end
|
86
|
+
|
78
87
|
def lead_user
|
79
88
|
user
|
80
89
|
end
|
@@ -82,6 +91,10 @@ class Workspace < ActiveRecord::Base
|
|
82
91
|
def missing_user
|
83
92
|
nil
|
84
93
|
end
|
94
|
+
|
95
|
+
def type
|
96
|
+
self.class.name
|
97
|
+
end
|
85
98
|
end
|
86
99
|
|
87
100
|
class Group < Workspace
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brainstem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mavenlink
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -112,16 +112,16 @@ dependencies:
|
|
112
112
|
name: mysql2
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - '='
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: 0.4.10
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: 0.4.10
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: database_cleaner
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -252,14 +252,17 @@ files:
|
|
252
252
|
- lib/brainstem/concerns/optional.rb
|
253
253
|
- lib/brainstem/concerns/presenter_dsl.rb
|
254
254
|
- lib/brainstem/controller_methods.rb
|
255
|
+
- lib/brainstem/dsl/array_block_field.rb
|
255
256
|
- lib/brainstem/dsl/association.rb
|
256
257
|
- lib/brainstem/dsl/associations_block.rb
|
257
258
|
- lib/brainstem/dsl/base_block.rb
|
259
|
+
- lib/brainstem/dsl/block_field.rb
|
258
260
|
- lib/brainstem/dsl/conditional.rb
|
259
261
|
- lib/brainstem/dsl/conditionals_block.rb
|
260
262
|
- lib/brainstem/dsl/configuration.rb
|
261
263
|
- lib/brainstem/dsl/field.rb
|
262
264
|
- lib/brainstem/dsl/fields_block.rb
|
265
|
+
- lib/brainstem/dsl/hash_block_field.rb
|
263
266
|
- lib/brainstem/help_text.txt
|
264
267
|
- lib/brainstem/preloader.rb
|
265
268
|
- lib/brainstem/presenter.rb
|
@@ -306,10 +309,13 @@ files:
|
|
306
309
|
- spec/brainstem/concerns/optional_spec.rb
|
307
310
|
- spec/brainstem/concerns/presenter_dsl_spec.rb
|
308
311
|
- spec/brainstem/controller_methods_spec.rb
|
312
|
+
- spec/brainstem/dsl/array_block_field_spec.rb
|
309
313
|
- spec/brainstem/dsl/association_spec.rb
|
314
|
+
- spec/brainstem/dsl/block_field_spec.rb
|
310
315
|
- spec/brainstem/dsl/conditional_spec.rb
|
311
316
|
- spec/brainstem/dsl/configuration_spec.rb
|
312
317
|
- spec/brainstem/dsl/field_spec.rb
|
318
|
+
- spec/brainstem/dsl/hash_block_field_spec.rb
|
313
319
|
- spec/brainstem/preloader_spec.rb
|
314
320
|
- spec/brainstem/presenter_collection_spec.rb
|
315
321
|
- spec/brainstem/presenter_spec.rb
|
@@ -384,10 +390,13 @@ test_files:
|
|
384
390
|
- spec/brainstem/concerns/optional_spec.rb
|
385
391
|
- spec/brainstem/concerns/presenter_dsl_spec.rb
|
386
392
|
- spec/brainstem/controller_methods_spec.rb
|
393
|
+
- spec/brainstem/dsl/array_block_field_spec.rb
|
387
394
|
- spec/brainstem/dsl/association_spec.rb
|
395
|
+
- spec/brainstem/dsl/block_field_spec.rb
|
388
396
|
- spec/brainstem/dsl/conditional_spec.rb
|
389
397
|
- spec/brainstem/dsl/configuration_spec.rb
|
390
398
|
- spec/brainstem/dsl/field_spec.rb
|
399
|
+
- spec/brainstem/dsl/hash_block_field_spec.rb
|
391
400
|
- spec/brainstem/preloader_spec.rb
|
392
401
|
- spec/brainstem/presenter_collection_spec.rb
|
393
402
|
- spec/brainstem/presenter_spec.rb
|