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
@@ -49,12 +49,14 @@ describe Brainstem::Concerns::InheritableConfiguration do
49
49
  end
50
50
 
51
51
  describe '#keys and #to_h' do
52
- let(:subclass) { Class.new(parent_class) }
52
+ let(:subclass) { Class.new(parent_class) }
53
53
  let(:subsubclass) { Class.new(subclass) }
54
+ let!(:proc_key) { Proc.new {} }
54
55
 
55
56
  before do
56
57
  parent_class.configuration['1'] = :a
57
58
  parent_class.configuration['2'] = :b
59
+ parent_class.configuration[proc_key] = :foo
58
60
 
59
61
  subclass.configuration['2'] = :c
60
62
  subclass.configuration['3'] = :d
@@ -64,12 +66,9 @@ describe Brainstem::Concerns::InheritableConfiguration do
64
66
  end
65
67
 
66
68
  it "returns the union of this class's keys with any parent keys" do
67
- expect(parent_class.configuration.keys).to eq ['1', '2']
68
- expect(parent_class.configuration.to_h).to eq({ '1' => :a, '2' => :b })
69
- expect(subclass.configuration.keys).to eq ['1', '2', '3']
70
- expect(subclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :d })
71
- expect(subsubclass.configuration.keys).to eq ['1', '2', '3', '4']
72
- expect(subsubclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :e, '4' => :f })
69
+ expect(parent_class.configuration.to_h).to eq({ '1' => :a, '2' => :b, proc_key => :foo })
70
+ expect(subclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :d, proc_key => :foo })
71
+ expect(subsubclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :e, '4' => :f, proc_key => :foo })
73
72
 
74
73
  # it doesn't mutate storage
75
74
  subclass.configuration.to_h['1'] = :new
@@ -82,14 +81,19 @@ describe Brainstem::Concerns::InheritableConfiguration do
82
81
  expect(parent_class.configuration['1']).to eq :a
83
82
  expect(parent_class.configuration['2']).to eq :b
84
83
  expect(parent_class.configuration['3']).to be_nil
84
+ expect(parent_class.configuration[proc_key]).to eq :foo
85
+
85
86
  expect(subclass.configuration['1']).to eq :a
86
87
  expect(subclass.configuration['2']).to eq :c
87
88
  expect(subclass.configuration['3']).to eq :d
88
89
  expect(subclass.configuration['4']).to be_nil
90
+ expect(subclass.configuration[proc_key]).to eq :foo
91
+
89
92
  expect(subsubclass.configuration['1']).to eq :a
90
93
  expect(subsubclass.configuration['2']).to eq :c
91
94
  expect(subsubclass.configuration['3']).to eq :e
92
95
  expect(subsubclass.configuration['4']).to eq :f
96
+ expect(subsubclass.configuration[proc_key]).to eq :foo
93
97
  end
94
98
 
95
99
  it "does not return nonheritable keys in the parent" do
@@ -97,8 +101,12 @@ describe Brainstem::Concerns::InheritableConfiguration do
97
101
  parent_class.configuration['nonheritable'] = "why yes, I am nonheritable"
98
102
  expect(subclass.configuration.keys).not_to include 'nonheritable'
99
103
 
100
- expect(subclass.configuration.to_h.keys).not_to include 'nonheritable'
101
- expect(subclass.configuration.has_key?('nonheritable')).to eq false
104
+ parent_class.configuration.nonheritable! proc_key
105
+ parent_class.configuration[proc_key] = "Not Inheritable!"
106
+ expect(subclass.configuration.keys).not_to include proc_key
107
+
108
+ expect(subclass.configuration.to_h.keys).not_to include proc_key
109
+ expect(subclass.configuration.has_key?(proc_key)).to eq false
102
110
  end
103
111
  end
104
112
 
@@ -298,6 +306,21 @@ describe Brainstem::Concerns::InheritableConfiguration do
298
306
  parent_class.configuration.nonheritable! :nonheritable
299
307
  expect(parent_class.configuration.nonheritable_keys.to_a).to eq ["nonheritable"]
300
308
  end
309
+
310
+ context "when key is a proc" do
311
+ let!(:proc_key) { Proc.new {} }
312
+
313
+ it "adds the key to the nonheritable attributes list" do
314
+ parent_class.configuration.nonheritable! proc_key
315
+ expect(parent_class.configuration.nonheritable_keys.to_a).to eq [proc_key]
316
+ end
317
+
318
+ it "dedupes" do
319
+ parent_class.configuration.nonheritable! proc_key
320
+ parent_class.configuration.nonheritable! proc_key
321
+ expect(parent_class.configuration.nonheritable_keys.to_a).to eq [proc_key]
322
+ end
323
+ end
301
324
  end
302
325
  end
303
326
 
@@ -21,16 +21,32 @@ require 'brainstem/concerns/presenter_dsl'
21
21
  # field :description, :string
22
22
  # field :updated_at, :datetime
23
23
  # field :dynamic_title, :string, dynamic: lambda { |model| model.title }
24
- # field :secret, :string, 'a secret, via secret_info',
24
+ # field :secret, :string,
25
+ # info: 'a secret, via secret_info',
25
26
  # via: :secret_info,
26
27
  # if: [:user_is_bob, :title_is_hello]
27
28
  #
29
+ # field :member_ids, :array,
30
+ # item_type: :integer,
31
+ # dynamic: lambda { |model| model.members.pluck(:id) }
32
+ #
28
33
  # with_options if: :user_is_bob do
29
34
  # field :bob_title, :string,
30
35
  # info: 'another name for the title, only for Bob',
31
36
  # via: :title
32
37
  # end
33
- # fields :nested_permissions do
38
+ #
39
+ # fields :members, :array,
40
+ # if: :user_is_bob,
41
+ # dynamic: lambda { |project| project.members } do
42
+ # field :name, :string,
43
+ # dynamic: lambda { |user| user.username }
44
+ # field :project_klass, :number,
45
+ # use_parent_value: false,
46
+ # dynamic: lambda { |project| project.class }
47
+ # end
48
+ #
49
+ # fields :nested_permissions, if: :user_is_bob do
34
50
  # field :something_title, :string, via: :title
35
51
  # field :random, :number, dynamic: lambda { rand }
36
52
  # end
@@ -118,7 +134,6 @@ describe Brainstem::Concerns::PresenterDSL do
118
134
  end
119
135
  end
120
136
 
121
-
122
137
  # sort_order :created_at, ".created_at"
123
138
  describe 'the sort_order block' do
124
139
  let(:value) { "widgets.created_at" }
@@ -159,7 +174,6 @@ describe Brainstem::Concerns::PresenterDSL do
159
174
  end
160
175
  end
161
176
 
162
-
163
177
  describe 'the conditional block' do
164
178
  before do
165
179
  presenter_class.conditionals do
@@ -218,11 +232,16 @@ describe Brainstem::Concerns::PresenterDSL do
218
232
  via: :secret_info,
219
233
  if: [:user_is_bob, :title_is_hello]
220
234
 
235
+ field :member_ids, :array,
236
+ item_type: :integer,
237
+ dynamic: lambda { |model| model.members.pluck(:id) }
238
+
221
239
  with_options if: :user_is_bob do
222
240
  field :bob_title, :string,
223
241
  info: 'another name for the title, only for Bob',
224
242
  via: :title
225
243
  end
244
+
226
245
  fields :nested_permissions do
227
246
  field :something_title, :string, via: :title
228
247
  field :random, :number, dynamic: lambda { rand }
@@ -231,16 +250,25 @@ describe Brainstem::Concerns::PresenterDSL do
231
250
  end
232
251
 
233
252
  it 'is stored in the configuration' do
234
- expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
235
- expect(presenter_class.configuration[:fields][:updated_at].type).to eq :datetime
253
+ expect(presenter_class.configuration[:fields].keys)
254
+ .to match_array %w[updated_at dynamic_title secret member_ids bob_title nested_permissions]
255
+
256
+ expect(presenter_class.configuration[:fields][:updated_at].type).to eq 'datetime'
236
257
  expect(presenter_class.configuration[:fields][:updated_at].description).to be_nil
237
- expect(presenter_class.configuration[:fields][:dynamic_title].type).to eq :string
258
+
259
+ expect(presenter_class.configuration[:fields][:dynamic_title].type).to eq 'string'
238
260
  expect(presenter_class.configuration[:fields][:dynamic_title].description).to be_nil
239
261
  expect(presenter_class.configuration[:fields][:dynamic_title].options[:dynamic]).to be_a(Proc)
240
- expect(presenter_class.configuration[:fields][:secret].type).to eq :string
262
+
263
+ expect(presenter_class.configuration[:fields][:secret].type).to eq 'string'
241
264
  expect(presenter_class.configuration[:fields][:secret].description).to be_nil
242
265
  expect(presenter_class.configuration[:fields][:secret].options).to eq({ via: :secret_info, if: [:user_is_bob, :title_is_hello] })
243
- expect(presenter_class.configuration[:fields][:bob_title].type).to eq :string
266
+
267
+ expect(presenter_class.configuration[:fields][:member_ids].type).to eq 'array'
268
+ expect(presenter_class.configuration[:fields][:member_ids].options[:item_type]).to eq 'integer'
269
+ expect(presenter_class.configuration[:fields][:member_ids].options[:dynamic]).to be_a(Proc)
270
+
271
+ expect(presenter_class.configuration[:fields][:bob_title].type).to eq 'string'
244
272
  expect(presenter_class.configuration[:fields][:bob_title].description).to eq 'another name for the title, only for Bob'
245
273
  expect(presenter_class.configuration[:fields][:bob_title].options).to eq(
246
274
  via: :title,
@@ -250,7 +278,7 @@ describe Brainstem::Concerns::PresenterDSL do
250
278
  end
251
279
 
252
280
  it 'handles nesting' do
253
- expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq :string
281
+ expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq 'string'
254
282
  expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].options[:via]).to eq :title
255
283
  expect(presenter_class.configuration[:fields][:nested_permissions][:random].options[:dynamic]).to be_a(Proc)
256
284
  end
@@ -263,11 +291,17 @@ describe Brainstem::Concerns::PresenterDSL do
263
291
  field :updated_at, :datetime, info: 'this time I have a description and condition'
264
292
  end
265
293
  end
266
- expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
267
- expect(subclass.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title title nested_permissions]
294
+
295
+ expect(presenter_class.configuration[:fields].keys).
296
+ to match_array %w[updated_at dynamic_title secret member_ids bob_title nested_permissions]
297
+ expect(subclass.configuration[:fields].keys).
298
+ to match_array %w[updated_at dynamic_title secret member_ids bob_title title nested_permissions]
299
+
268
300
  expect(presenter_class.configuration[:fields][:updated_at].description).to be_nil
269
301
  expect(presenter_class.configuration[:fields][:updated_at].options).to eq({})
270
- expect(subclass.configuration[:fields][:updated_at].description).to eq 'this time I have a description and condition'
302
+
303
+ expect(subclass.configuration[:fields][:updated_at].description).
304
+ to eq 'this time I have a description and condition'
271
305
  expect(subclass.configuration[:fields][:updated_at].options).to eq(
272
306
  if: [:some_condition, :some_other_condition],
273
307
  info: 'this time I have a description and condition'
@@ -320,18 +354,21 @@ describe Brainstem::Concerns::PresenterDSL do
320
354
  field :something, :string, via: :title
321
355
  end
322
356
  end
323
- expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
324
- expect(subclass.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions new_nested_permissions]
325
357
 
326
- expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq :string
327
- expect(presenter_class.configuration[:fields][:nested_permissions][:random].type).to eq :number
358
+ expect(presenter_class.configuration[:fields].keys)
359
+ .to match_array %w[updated_at dynamic_title secret member_ids bob_title nested_permissions]
360
+ expect(subclass.configuration[:fields].keys)
361
+ .to match_array %w[updated_at dynamic_title secret member_ids bob_title nested_permissions new_nested_permissions]
362
+
363
+ expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq 'string'
364
+ expect(presenter_class.configuration[:fields][:nested_permissions][:random].type).to eq 'number'
328
365
  expect(presenter_class.configuration[:fields][:nested_permissions][:new]).to be_nil
329
366
  expect(presenter_class.configuration[:fields][:nested_permissions][:deeper]).to be_nil
330
367
  expect(presenter_class.configuration[:fields][:new_nested_permissions]).to be_nil
331
368
 
332
- expect(subclass.configuration[:fields][:nested_permissions][:something_title].type).to eq :number # changed this
333
- expect(subclass.configuration[:fields][:nested_permissions][:random].type).to eq :number
334
- expect(subclass.configuration[:fields][:nested_permissions][:new].type).to eq :string
369
+ expect(subclass.configuration[:fields][:nested_permissions][:something_title].type).to eq 'number' # changed this
370
+ expect(subclass.configuration[:fields][:nested_permissions][:random].type).to eq 'number'
371
+ expect(subclass.configuration[:fields][:nested_permissions][:new].type).to eq 'string'
335
372
  expect(subclass.configuration[:fields][:nested_permissions][:deeper][:something]).to be_present
336
373
  expect(subclass.configuration[:fields][:new_nested_permissions]).to be_present
337
374
  end
@@ -345,7 +382,7 @@ describe Brainstem::Concerns::PresenterDSL do
345
382
 
346
383
  it "is stored in the configuration correctly" do
347
384
  expect(presenter_class.configuration[:fields].keys).to include('synced_at')
348
- expect(presenter_class.configuration[:fields][:synced_at].type).to eq :datetime
385
+ expect(presenter_class.configuration[:fields][:synced_at].type).to eq 'datetime'
349
386
  expect(presenter_class.configuration[:fields][:synced_at].description).to eq 'Last time the object was synced'
350
387
  end
351
388
  end
@@ -539,21 +576,58 @@ describe Brainstem::Concerns::PresenterDSL do
539
576
 
540
577
  it "creates an entry in the filters configuration" do
541
578
  my_proc = Proc.new { 1 }
542
- presenter_class.filter(:foo, :default => true, &my_proc)
579
+ presenter_class.filter(:foo, :string, :default => true, :items => [:a, :b], &my_proc)
543
580
 
544
- expect(foo).to eq({ "default" => true, "value" => my_proc })
581
+ expect(foo).to eq({ "default" => true, "value" => my_proc, "type" => "string", "items" => [:a, :b] })
545
582
  end
546
583
 
547
584
  it "accepts names without blocks" do
548
- presenter_class.filter(:foo)
585
+ presenter_class.filter(:foo, :string, :items => [:a, :b])
549
586
  expect(foo[:value]).to be_nil
587
+ expect(foo[:type]).to eq("string")
588
+ expect(foo[:items]).to eq([:a, :b])
550
589
  end
551
590
 
552
591
  it "records the info option" do
553
- presenter_class.filter(:foo, :info => "This is documented.")
592
+ presenter_class.filter(:foo, :integer, :info => "This is documented.", :items => [:a, :b])
554
593
  expect(foo[:info]).to eq "This is documented."
594
+ expect(foo[:type]).to eq "integer"
595
+ expect(foo[:items]).to eq([:a, :b])
596
+ end
597
+
598
+ context "when filter is of array type" do
599
+ it "records the item_type option" do
600
+ presenter_class.filter(:foo, :array, :item_type => :integer)
601
+ expect(foo[:type]).to eq "array"
602
+ expect(foo[:item_type]).to eq("integer")
603
+ end
604
+
605
+ it "defaults item_type to string if not specified" do
606
+ presenter_class.filter(:foo, :array)
607
+ expect(foo[:type]).to eq "array"
608
+ expect(foo[:item_type]).to eq("string")
609
+ end
555
610
  end
556
611
 
612
+
613
+ context "when type is not specified" do
614
+ before do
615
+ mock(presenter_class).deprecated_type_warning
616
+ end
617
+
618
+ it "adds a deprecation warning and creates an entry in the filters configuration" do
619
+ my_proc = Proc.new { 1 }
620
+ presenter_class.filter(:foo, :default => true, &my_proc)
621
+
622
+ expect(foo).to eq({ "default" => true, "value" => my_proc, "type" => "string" })
623
+ end
624
+
625
+ it "adds a deprecation warning and records the info option" do
626
+ presenter_class.filter(:foo, :info => "This is documented.")
627
+ expect(foo[:info]).to eq "This is documented."
628
+ expect(foo[:type]).to eq "string"
629
+ end
630
+ end
557
631
  end
558
632
 
559
633
  describe ".search" do
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/dsl/array_block_field'
3
+
4
+ describe Brainstem::DSL::ArrayBlockField do
5
+ let(:name) { :tasks }
6
+ let(:type) { :array }
7
+ let(:description) { 'the title of this model' }
8
+ let(:options) { { info: description, via: :tasks } }
9
+ let(:nested_field) { Brainstem::DSL::ArrayBlockField.new(name, type, options) }
10
+ let(:model) { Workspace.find(1) }
11
+ let(:tasks) { Task.where(workspace_id: model.id).order(:id).to_a }
12
+
13
+ before do
14
+ expect(tasks).to_not be_empty
15
+ expect(nested_field.configuration.keys).to be_empty
16
+
17
+ # Add sub fields to the array block field.
18
+ nested_field.configuration[:name] = Brainstem::DSL::Field.new(:name, type, {})
19
+ nested_field.configuration[:parent_name] = Brainstem::DSL::Field.new(:parent_name, type,
20
+ dynamic: -> (model) { model.parent.try(:name) }
21
+ )
22
+ nested_field.configuration[:secret] = Brainstem::DSL::Field.new(:secret, type,
23
+ via: :secret_info,
24
+ use_parent_value: false
25
+ )
26
+
27
+ expect(nested_field.configuration.keys).to eq(%w(name parent_name secret))
28
+ end
29
+
30
+ describe '#run_on' do
31
+ let(:context) { { } }
32
+ let(:helper_instance) { Object.new }
33
+ let(:presented_field_data) {
34
+ tasks.map do |task|
35
+ { 'name' => task.name, 'parent_name' => task.parent.try(:name), 'secret' => model.secret_info }
36
+ end
37
+ }
38
+
39
+ it 'returns an array of hashes with sub nested properties' do
40
+ expect(nested_field.run_on(model, context)).to eq(presented_field_data)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/dsl/block_field'
3
+
4
+ describe Brainstem::DSL::BlockField do
5
+ let(:name) { :tasks }
6
+ let(:type) { :hash }
7
+ let(:description) { 'the title of this model' }
8
+ let(:options) { { info: description } }
9
+ let(:nested_field) { Brainstem::DSL::BlockField.new(name, type, options) }
10
+ let(:model) { Workspace.find(1) }
11
+
12
+ describe 'configuration' do
13
+ context 'when parent field specified' do
14
+ let(:parent_field) { Brainstem::DSL::BlockField.new(name, type, options) }
15
+ let(:nested_field) { Brainstem::DSL::BlockField.new(name, type, options, parent_field) }
16
+ let(:inherited_field) do
17
+ Brainstem::DSL::Field.new(:parent_name, type, dynamic: -> (model) { model.parent.try(:name) })
18
+ end
19
+ let(:overriden_field) do
20
+ Brainstem::DSL::Field.new(:name, type, dynamic: -> (model) { "Formatted #{model.name}" })
21
+ end
22
+
23
+ before do
24
+ parent_field.configuration[:name] = Brainstem::DSL::Field.new(:name, type, {})
25
+ parent_field.configuration[:parent_name] = inherited_field
26
+
27
+ nested_field.configuration[:name] = overriden_field
28
+ nested_field.configuration[:parent_title] = Brainstem::DSL::Field.new(:parent_title, type,
29
+ dynamic: -> (model) { model.parent.try(:name) }
30
+ )
31
+
32
+ expect(nested_field.configuration.keys).to include('name', 'parent_title')
33
+ end
34
+
35
+ it 'inherits sub fields from the parent field' do
36
+ expect(nested_field.configuration.keys).to include('parent_name')
37
+ expect(nested_field.configuration[:parent_name]).to eq(inherited_field)
38
+ end
39
+
40
+ it 'override keys inherits from the parent' do
41
+ expect(nested_field.configuration.keys).to include('name')
42
+ expect(nested_field.configuration[:name]).to eq(overriden_field)
43
+ end
44
+ end
45
+
46
+ context 'when parent field is not specified' do
47
+ let(:nested_field) { Brainstem::DSL::BlockField.new(name, type, options) }
48
+
49
+ before do
50
+ nested_field.configuration[:name] = Brainstem::DSL::Field.new(:name, type, {})
51
+ nested_field.configuration[:parent_title] = Brainstem::DSL::Field.new(:parent_title, type,
52
+ dynamic: -> (model) { model.parent.try(:name) }
53
+ )
54
+ end
55
+
56
+ it 'only has sub fields defined on itself' do
57
+ expect(nested_field.configuration.keys).to match_array(['name', 'parent_title'])
58
+ end
59
+ end
60
+ end
61
+
62
+ describe 'self.for' do
63
+ subject { described_class.for(name, type, options) }
64
+
65
+ context 'when given an array type' do
66
+ let(:type) { :array }
67
+
68
+ it 'returns a new ArrayBlockField' do
69
+ expect(subject).to be_instance_of(Brainstem::DSL::ArrayBlockField)
70
+ end
71
+ end
72
+
73
+ context 'when given a hash type' do
74
+ let(:type) { :hash }
75
+
76
+ it 'returns a new HashBlockField' do
77
+ expect(subject).to be_instance_of(Brainstem::DSL::HashBlockField)
78
+ end
79
+ end
80
+
81
+ context 'when given an unknown type' do
82
+ let(:type) { :unknown }
83
+
84
+ it 'raises an error' do
85
+ expect { subject }.to raise_error(StandardError)
86
+ end
87
+ end
88
+ end
89
+
90
+ describe 'evaluate_value_on' do
91
+ let(:context) { {} }
92
+ let(:helper_instance) { Object.new }
93
+
94
+ context 'when lookup option is specifed' do
95
+ let(:context) {
96
+ {
97
+ lookup: Brainstem::Presenter.new.send(:empty_lookup_cache, [name.to_s], []),
98
+ models: [model]
99
+ }
100
+ }
101
+
102
+ context 'when lookup_fetch option is not specified' do
103
+ let(:options) do
104
+ { lookup: lambda { |models| Hash[models.map { |model| [model.id, model.tasks.to_a] }] } }
105
+ end
106
+
107
+ before do
108
+ expect(options).to_not have_key(:lookup_fetch)
109
+ end
110
+
111
+ it 'returns the value from the lookup cache' do
112
+ expect(nested_field.evaluate_value_on(model, context, helper_instance)).to eq(model.tasks.to_a)
113
+ end
114
+ end
115
+
116
+ context 'when lookup_fetch option is specified' do
117
+ let(:options) do
118
+ {
119
+ lookup: lambda { |models| Hash[models.map { |model| [model.id + 10, model.tasks.to_a] }] },
120
+ lookup_fetch: lambda { |lookup, model| lookup[model.id + 10] }
121
+ }
122
+ end
123
+
124
+ before do
125
+ expect(options).to have_key(:lookup_fetch)
126
+ end
127
+
128
+ it 'returns the value from the lookup cache using the lookup fetch' do
129
+ expect(nested_field.evaluate_value_on(model, context, helper_instance)).to eq(model.tasks.to_a)
130
+ end
131
+ end
132
+ end
133
+
134
+ context 'when dynamic option is specified' do
135
+ let(:options) { { dynamic: lambda { |model| model.tasks.to_a } } }
136
+
137
+ it 'calls the :dynamic lambda in the context of the given instance' do
138
+ expect(nested_field.evaluate_value_on(model, context, helper_instance)).to eq(model.tasks.to_a)
139
+ end
140
+ end
141
+
142
+ context 'when via option is specified' do
143
+ let(:options) { { via: :tasks } }
144
+
145
+ it 'calls the method name in the :via option in the context of the given instance' do
146
+ expect(nested_field.evaluate_value_on(model, context, helper_instance)).to eq(model.tasks.to_a)
147
+ end
148
+ end
149
+
150
+ context 'when none of the options are specified' do
151
+ let(:options) { {} }
152
+
153
+ it 'should raise error' do
154
+ expect {
155
+ nested_field.evaluate_value_on(model, context, helper_instance)
156
+ }.to raise_error(StandardError)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe 'use_parent_value?' do
162
+ let(:field) { Brainstem::DSL::Field.new(:type, type, options) }
163
+
164
+ subject { nested_field.use_parent_value?(field) }
165
+
166
+ context 'when sub field does not specify use_parent_value option' do
167
+ let(:options) { { info: description } }
168
+
169
+ it { is_expected.to be_truthy }
170
+ end
171
+
172
+ context 'when sub field specifies use_parent_value option' do
173
+ let(:options) { { use_parent_value: use_parent_value } }
174
+
175
+ context 'when set to true' do
176
+ let(:use_parent_value) { true }
177
+
178
+ it { is_expected.to be_truthy }
179
+ end
180
+
181
+ context 'when set to false' do
182
+ let(:use_parent_value) { false }
183
+
184
+ it { is_expected.to be_falsey }
185
+ end
186
+ end
187
+ end
188
+ end