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.
- 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
|