brainstem 1.1.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -4
  3. data/Gemfile.lock +9 -9
  4. data/README.md +134 -37
  5. data/brainstem.gemspec +1 -1
  6. data/lib/brainstem/api_docs/endpoint.rb +40 -18
  7. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
  8. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
  9. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
  10. data/lib/brainstem/api_docs/presenter.rb +3 -7
  11. data/lib/brainstem/concerns/controller_dsl.rb +138 -14
  12. data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
  13. data/lib/brainstem/dsl/array_block_field.rb +25 -0
  14. data/lib/brainstem/dsl/block_field.rb +69 -0
  15. data/lib/brainstem/dsl/configuration.rb +13 -5
  16. data/lib/brainstem/dsl/field.rb +15 -1
  17. data/lib/brainstem/dsl/fields_block.rb +20 -2
  18. data/lib/brainstem/dsl/hash_block_field.rb +30 -0
  19. data/lib/brainstem/presenter.rb +10 -6
  20. data/lib/brainstem/presenter_validator.rb +20 -11
  21. data/lib/brainstem/version.rb +1 -1
  22. data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
  23. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
  24. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
  25. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
  26. data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
  27. data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
  28. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
  29. data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
  30. data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
  31. data/spec/brainstem/dsl/block_field_spec.rb +188 -0
  32. data/spec/brainstem/dsl/field_spec.rb +86 -20
  33. data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
  34. data/spec/brainstem/presenter_collection_spec.rb +24 -24
  35. data/spec/brainstem/presenter_spec.rb +233 -9
  36. data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
  37. data/spec/spec_helpers/presenters.rb +8 -0
  38. data/spec/spec_helpers/schema.rb +13 -0
  39. metadata +15 -6
@@ -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