granite-form 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -2
  3. data/.github/workflows/{ci.yml → ruby.yml} +22 -4
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +3 -3
  6. data/Appraisals +1 -2
  7. data/CHANGELOG.md +14 -0
  8. data/README.md +0 -2
  9. data/Rakefile +4 -0
  10. data/docker-compose.yml +14 -0
  11. data/gemfiles/rails.5.0.gemfile +0 -1
  12. data/gemfiles/rails.5.1.gemfile +0 -1
  13. data/gemfiles/rails.5.2.gemfile +0 -1
  14. data/granite-form.gemspec +16 -15
  15. data/lib/granite/form/active_record/associations.rb +1 -1
  16. data/lib/granite/form/base.rb +1 -2
  17. data/lib/granite/form/config.rb +10 -10
  18. data/lib/granite/form/errors.rb +0 -15
  19. data/lib/granite/form/model/associations/base.rb +0 -4
  20. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  21. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  22. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  23. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  24. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  25. data/lib/granite/form/model/associations/nested_attributes.rb +8 -8
  26. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  27. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  28. data/lib/granite/form/model/associations/references_many.rb +0 -32
  29. data/lib/granite/form/model/associations/references_one.rb +0 -28
  30. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
  33. data/lib/granite/form/model/associations/reflections/references_one.rb +3 -3
  34. data/lib/granite/form/model/associations/reflections/singular.rb +0 -8
  35. data/lib/granite/form/model/associations.rb +0 -6
  36. data/lib/granite/form/model/attributes/attribute.rb +1 -1
  37. data/lib/granite/form/model/attributes/base.rb +14 -17
  38. data/lib/granite/form/model/attributes/collection.rb +1 -1
  39. data/lib/granite/form/model/attributes/dictionary.rb +1 -1
  40. data/lib/granite/form/model/attributes/localized.rb +1 -1
  41. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  42. data/lib/granite/form/model/attributes/reference_one.rb +1 -9
  43. data/lib/granite/form/model/attributes/reflections/attribute.rb +0 -6
  44. data/lib/granite/form/model/attributes/reflections/base.rb +9 -12
  45. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -10
  46. data/lib/granite/form/model/persistence.rb +1 -19
  47. data/lib/granite/form/model/validations/nested.rb +1 -1
  48. data/lib/granite/form/model.rb +0 -2
  49. data/lib/granite/form/types/active_support/time_zone.rb +22 -0
  50. data/lib/granite/form/types/array.rb +17 -0
  51. data/lib/granite/form/types/big_decimal.rb +15 -0
  52. data/lib/granite/form/types/boolean.rb +38 -0
  53. data/lib/granite/form/types/date.rb +15 -0
  54. data/lib/granite/form/types/date_time.rb +15 -0
  55. data/lib/granite/form/types/float.rb +15 -0
  56. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
  57. data/lib/granite/form/types/integer.rb +13 -0
  58. data/lib/granite/form/types/object.rb +30 -0
  59. data/lib/granite/form/types/string.rb +13 -0
  60. data/lib/granite/form/types/time.rb +15 -0
  61. data/lib/granite/form/types/uuid.rb +22 -0
  62. data/lib/granite/form/types.rb +15 -0
  63. data/lib/granite/form/version.rb +1 -1
  64. data/lib/granite/form.rb +19 -118
  65. data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +16 -18
  66. data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
  67. data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
  68. data/spec/granite/form/extensions_spec.rb +12 -0
  69. data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +29 -305
  70. data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +27 -212
  71. data/spec/granite/form/model/associations/nested_attributes_spec.rb +23 -0
  72. data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
  73. data/spec/granite/form/model/associations/references_many_spec.rb +251 -0
  74. data/spec/granite/form/model/associations/references_one_spec.rb +173 -0
  75. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  76. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  77. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  78. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +1 -1
  79. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +1 -22
  80. data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -3
  81. data/spec/{lib/granite → granite}/form/model/associations_spec.rb +3 -24
  82. data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
  83. data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
  84. data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
  85. data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
  86. data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
  87. data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
  88. data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
  89. data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
  90. data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
  91. data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
  92. data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
  93. data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
  94. data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
  95. data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
  96. data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +1 -1
  97. data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -2
  98. data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
  99. data/spec/{lib/granite → granite}/form/model/representation_spec.rb +0 -0
  100. data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
  101. data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +2 -4
  102. data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +57 -15
  103. data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
  104. data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
  105. data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
  106. data/spec/granite/form/types/array_spec.rb +13 -0
  107. data/spec/granite/form/types/big_decimal_spec.rb +19 -0
  108. data/spec/granite/form/types/boolean_spec.rb +21 -0
  109. data/spec/granite/form/types/date_spec.rb +18 -0
  110. data/spec/granite/form/types/date_time_spec.rb +20 -0
  111. data/spec/granite/form/types/float_spec.rb +19 -0
  112. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
  113. data/spec/granite/form/types/integer_spec.rb +18 -0
  114. data/spec/granite/form/types/object_spec.rb +40 -0
  115. data/spec/granite/form/types/string_spec.rb +13 -0
  116. data/spec/granite/form/types/time_spec.rb +31 -0
  117. data/spec/granite/form/types/uuid_spec.rb +21 -0
  118. data/spec/{lib/granite → granite}/form_spec.rb +0 -0
  119. data/spec/spec_helper.rb +0 -15
  120. data/spec/support/active_record.rb +20 -0
  121. data/spec/{shared → support/shared}/nested_attribute_examples.rb +3 -21
  122. data/spec/support/shared/type_examples.rb +7 -0
  123. metadata +173 -123
  124. data/.github/workflows/main.yml +0 -29
  125. data/gemfiles/rails.4.2.gemfile +0 -15
  126. data/lib/granite/form/model/callbacks.rb +0 -72
  127. data/lib/granite/form/model/lifecycle.rb +0 -309
  128. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +0 -119
  129. data/spec/lib/granite/form/model/associations/references_many_spec.rb +0 -572
  130. data/spec/lib/granite/form/model/associations/references_one_spec.rb +0 -445
  131. data/spec/lib/granite/form/model/callbacks_spec.rb +0 -337
  132. data/spec/lib/granite/form/model/lifecycle_spec.rb +0 -356
  133. data/spec/lib/granite/form/model/typecasting_spec.rb +0 -193
@@ -3,7 +3,8 @@ require 'spec_helper'
3
3
  describe Granite::Form::Model::Associations::EmbedsOne do
4
4
  before do
5
5
  stub_model(:author) do
6
- include Granite::Form::Model::Lifecycle
6
+ include Granite::Form::Model::Persistence
7
+ include Granite::Form::Model::Associations
7
8
 
8
9
  attribute :name, String
9
10
  validates :name, presence: true
@@ -61,12 +62,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
61
62
  ])
62
63
  end
63
64
 
64
- specify do
65
- expect { association.create(name: 'Author1') }
66
- .to change { book.callbacks }
67
- .to([[:before_add, author1], [:after_add, author1]])
68
- end
69
-
70
65
  specify do
71
66
  expect { association.writer(author1) }
72
67
  .to change { book.callbacks }
@@ -118,7 +113,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
118
113
  let(:author) { Author.new(name: 'Author') }
119
114
 
120
115
  specify { expect(association.build.embedder).to eq(book) }
121
- specify { expect(association.create.embedder).to eq(book) }
122
116
  specify do
123
117
  expect { association.writer(author) }
124
118
  .to change { author.embedder }.from(nil).to(book)
@@ -156,7 +150,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
156
150
  end
157
151
 
158
152
  specify { expect(association.build(name: 'Author').name).to eq('AuthorBook') }
159
- specify { expect(association.create(name: 'Author').name).to eq('AuthorBook') }
160
153
  end
161
154
  end
162
155
 
@@ -175,132 +168,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
175
168
  end
176
169
  end
177
170
 
178
- describe '#create' do
179
- specify { expect(association.create).to be_a Author }
180
- specify { expect(association.create).not_to be_persisted }
181
-
182
- specify { expect(association.create(name: 'Fred')).to be_a Author }
183
- specify { expect(association.create(name: 'Fred')).to be_persisted }
184
-
185
- specify do
186
- expect { association.create }
187
- .not_to change { book.read_attribute(:author) }
188
- end
189
- specify do
190
- expect { association.create(name: 'Fred') }
191
- .to change { book.read_attribute(:author) }
192
- .from(nil).to('name' => 'Fred')
193
- end
194
-
195
- specify do
196
- expect { existing_association.create }
197
- .not_to change { existing_book.read_attribute(:author) }
198
- end
199
- specify do
200
- expect { existing_association.create(name: 'Fred') }
201
- .to change { existing_book.read_attribute(:author) }
202
- .from('name' => 'Johny').to('name' => 'Fred')
203
- end
204
- end
205
-
206
- describe '#create!' do
207
- specify { expect { association.create! }.to raise_error Granite::Form::ValidationError }
208
- specify do
209
- expect { muffle(Granite::Form::ValidationError) { association.create! } }
210
- .to change { association.target }
211
- .from(nil).to(an_instance_of(Author))
212
- end
213
-
214
- specify { expect(association.create!(name: 'Fred')).to be_a Author }
215
- specify { expect(association.create!(name: 'Fred')).to be_persisted }
216
-
217
- specify do
218
- expect { muffle(Granite::Form::ValidationError) { association.create! } }
219
- .not_to change { book.read_attribute(:author) }
220
- end
221
- specify do
222
- expect { muffle(Granite::Form::ValidationError) { association.create! } }
223
- .to change { association.reader.try(:attributes) }
224
- .from(nil).to('name' => nil)
225
- end
226
- specify do
227
- expect { association.create(name: 'Fred') }
228
- .to change { book.read_attribute(:author) }
229
- .from(nil).to('name' => 'Fred')
230
- end
231
-
232
- specify do
233
- expect { muffle(Granite::Form::ValidationError) { existing_association.create! } }
234
- .not_to change { existing_book.read_attribute(:author) }
235
- end
236
- specify do
237
- expect { muffle(Granite::Form::ValidationError) { existing_association.create! } }
238
- .to change { existing_association.reader.try(:attributes) }
239
- .from('name' => 'Johny').to('name' => nil)
240
- end
241
- specify do
242
- expect { existing_association.create!(name: 'Fred') }
243
- .to change { existing_book.read_attribute(:author) }
244
- .from('name' => 'Johny').to('name' => 'Fred')
245
- end
246
- end
247
-
248
- describe '#apply_changes' do
249
- specify do
250
- association.build
251
- expect { association.apply_changes }
252
- .not_to change { association.target.persisted? }.from(false)
253
- end
254
- specify do
255
- association.build(name: 'Fred')
256
- expect { association.apply_changes }
257
- .to change { association.target.persisted? }.to(true)
258
- end
259
- specify do
260
- existing_association.target.mark_for_destruction
261
- expect { existing_association.apply_changes }
262
- .to change { existing_association.target }.to(nil)
263
- end
264
- specify do
265
- existing_association.target.destroy!
266
- expect { existing_association.apply_changes }
267
- .to change { existing_association.target }.to(nil)
268
- end
269
- specify do
270
- existing_association.target.mark_for_destruction
271
- expect { existing_association.apply_changes }
272
- .to change { existing_association.destroyed.try(:name) }.from(nil).to('Johny')
273
- end
274
- specify do
275
- existing_association.target.destroy!
276
- expect { existing_association.apply_changes }
277
- .to change { existing_association.destroyed.try(:name) }.from(nil).to('Johny')
278
- end
279
- end
280
-
281
- describe '#apply_changes!' do
282
- specify do
283
- association.build
284
- expect { association.apply_changes! }
285
- .to raise_error Granite::Form::AssociationChangesNotApplied
286
- end
287
- specify do
288
- association.build(name: 'Fred')
289
- expect { association.apply_changes! }
290
- .to change { association.target.persisted? }.to(true)
291
- end
292
- specify do
293
- existing_association.target.mark_for_destruction
294
- expect { existing_association.apply_changes! }
295
- .to change { existing_association.target }.to(nil)
296
- end
297
- specify do
298
- existing_association.target.destroy!
299
- expect { existing_association.apply_changes! }
300
- .to change { existing_association.target }.to(nil)
301
- end
302
- end
303
-
304
171
  describe '#target' do
305
172
  specify { expect(association.target).to be_nil }
306
173
  specify { expect(existing_association.target).to eq(existing_book.author) }
@@ -369,6 +236,31 @@ describe Granite::Form::Model::Associations::EmbedsOne do
369
236
  end
370
237
  end
371
238
 
239
+ describe '#sync' do
240
+ let!(:author) { association.build(name: 'Fred') }
241
+
242
+ specify { expect { association.sync }.to change { book.read_attribute(:author) }.from(nil).to('name' => 'Fred') }
243
+
244
+ context 'when embedding is nested' do
245
+ before do
246
+ Author.class_eval do
247
+ include Granite::Form::Model::Associations
248
+
249
+ embeds_many :reviews do
250
+ attribute :rating, Integer
251
+ end
252
+ end
253
+
254
+ author.reviews.build(rating: 7)
255
+ end
256
+
257
+ specify do
258
+ expect { association.sync }.to change { book.read_attribute(:author) }
259
+ .from(nil).to('name' => 'Fred', 'reviews' => [{'rating' => 7}])
260
+ end
261
+ end
262
+ end
263
+
372
264
  describe '#clear' do
373
265
  specify { expect(association.clear).to eq(true) }
374
266
  specify { expect { association.clear }.not_to change { association.reader } }
@@ -378,28 +270,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
378
270
  expect { existing_association.clear }
379
271
  .to change { existing_association.reader.try(:attributes) }.from('name' => 'Johny').to(nil)
380
272
  end
381
- specify do
382
- expect { existing_association.clear }
383
- .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to(nil)
384
- end
385
-
386
- context do
387
- before { Author.send(:include, Granite::Form::Model::Callbacks) }
388
- if ActiveModel.version >= Gem::Version.new('5.0.0')
389
- before { Author.before_destroy { throw :abort } }
390
- else
391
- before { Author.before_destroy { false } }
392
- end
393
- specify { expect(existing_association.clear).to eq(false) }
394
- specify do
395
- expect { existing_association.clear }
396
- .not_to change { existing_association.reader }
397
- end
398
- specify do
399
- expect { existing_association.clear }
400
- .not_to change { existing_book.read_attribute(:author).symbolize_keys }
401
- end
402
- end
403
273
  end
404
274
 
405
275
  describe '#reader' do
@@ -441,23 +311,6 @@ describe Granite::Form::Model::Associations::EmbedsOne do
441
311
  expect { association.writer(new_author) }
442
312
  .to change { association.reader.try(:attributes) }.from(nil).to('name' => 'Morty')
443
313
  end
444
- specify do
445
- expect { association.writer(new_author) }
446
- .to change { book.read_attribute(:author) }.from(nil).to('name' => 'Morty')
447
- end
448
-
449
- specify do
450
- expect { association.writer(invalid_author) }
451
- .to raise_error Granite::Form::AssociationChangesNotApplied
452
- end
453
- specify do
454
- expect { muffle(Granite::Form::AssociationChangesNotApplied) { association.writer(invalid_author) } }
455
- .not_to change { association.reader }
456
- end
457
- specify do
458
- expect { muffle(Granite::Form::AssociationChangesNotApplied) { association.writer(invalid_author) } }
459
- .not_to change { book.read_attribute(:author) }
460
- end
461
314
  end
462
315
 
463
316
  context 'persisted owner' do
@@ -468,32 +321,16 @@ describe Granite::Form::Model::Associations::EmbedsOne do
468
321
 
469
322
  specify { expect(association.writer(nil)).to be_nil }
470
323
  specify { expect(association.writer(new_author)).to eq(new_author) }
471
- specify do
472
- expect { association.writer(nil) }
473
- .not_to change { book.read_attribute(:author) }
474
- end
475
324
  specify do
476
325
  expect { association.writer(new_author) }
477
326
  .to change { association.reader.try(:attributes) }.from(nil).to('name' => 'Morty')
478
327
  end
479
- specify do
480
- expect { association.writer(new_author) }
481
- .not_to change { book.read_attribute(:author) }
482
- end
483
328
 
484
329
  specify do
485
330
  expect { association.writer(invalid_author) }
486
331
  .to change { association.reader.try(:attributes) }.from(nil).to('name' => nil)
487
332
  end
488
- specify do
489
- expect { association.writer(invalid_author) }
490
- .not_to change { book.read_attribute(:author) }
491
- end
492
333
 
493
- specify do
494
- expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer(stub_model(:dummy).new) } }
495
- .not_to change { existing_book.read_attribute(:author) }
496
- end
497
334
  specify do
498
335
  expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer(stub_model(:dummy).new) } }
499
336
  .not_to change { existing_association.reader }
@@ -501,33 +338,11 @@ describe Granite::Form::Model::Associations::EmbedsOne do
501
338
 
502
339
  specify { expect(existing_association.writer(nil)).to be_nil }
503
340
  specify { expect(existing_association.writer(new_author)).to eq(new_author) }
504
- specify do
505
- expect { existing_association.writer(nil) }
506
- .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to(nil)
507
- end
508
341
  specify do
509
342
  expect { existing_association.writer(new_author) }
510
343
  .to change { existing_association.reader.try(:attributes) }
511
344
  .from('name' => 'Johny').to('name' => 'Morty')
512
345
  end
513
- specify do
514
- expect { existing_association.writer(new_author) }
515
- .to change { existing_book.read_attribute(:author) }
516
- .from('name' => 'Johny').to('name' => 'Morty')
517
- end
518
-
519
- specify do
520
- expect { existing_association.writer(invalid_author) }
521
- .to raise_error Granite::Form::AssociationChangesNotApplied
522
- end
523
- specify do
524
- expect { muffle(Granite::Form::AssociationChangesNotApplied) { existing_association.writer(invalid_author) } }
525
- .not_to change { existing_association.reader }
526
- end
527
- specify do
528
- expect { muffle(Granite::Form::AssociationChangesNotApplied) { existing_association.writer(invalid_author) } }
529
- .not_to change { existing_book.read_attribute(:author) }
530
- end
531
346
  end
532
347
  end
533
348
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations::NestedAttributes do
4
+ context '' do
5
+ before do
6
+ stub_model :user do
7
+ include Granite::Form::Model::Associations
8
+
9
+ attribute :email, String
10
+ embeds_one :profile
11
+ embeds_many :projects
12
+
13
+ accepts_nested_attributes_for :profile, :projects
14
+ end
15
+ end
16
+
17
+ include_examples 'nested attributes'
18
+ end
19
+
20
+ describe '#assign_attributes' do
21
+ specify 'invent a good example'
22
+ end
23
+ end
@@ -0,0 +1,251 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations::ReferencesMany do
4
+ before do
5
+ stub_model(:dummy)
6
+ stub_class(:author, ActiveRecord::Base) do
7
+ scope :name_starts_with_a, -> { where('name ILIKE "a%"') }
8
+
9
+ validates :name, presence: true
10
+ end
11
+
12
+ stub_model(:book) do
13
+ include Granite::Form::Model::Persistence
14
+ include Granite::Form::Model::Associations
15
+
16
+ attribute :title, String
17
+ references_many :authors
18
+ end
19
+ end
20
+
21
+ let(:author) { Author.create!(name: 'Rick') }
22
+ let(:other) { Author.create!(name: 'Ben') }
23
+
24
+ let(:book) { Book.new }
25
+ let(:association) { book.association(:authors) }
26
+
27
+ let(:existing_book) { Book.instantiate title: 'Genesis', author_ids: [author.id] }
28
+ let(:existing_association) { existing_book.association(:authors) }
29
+
30
+ describe 'book#association' do
31
+ specify { expect(association).to be_a described_class }
32
+ specify { expect(association).to eq(book.association(:authors)) }
33
+ end
34
+
35
+ describe 'book#inspect' do
36
+ specify { expect(existing_book.inspect).to eq(<<~STR.chomp) }
37
+ #<Book authors: #<ReferencesMany [#{author.inspect}]>, title: "Genesis", author_ids: [#{author.id}]>
38
+ STR
39
+ end
40
+
41
+ describe '#scope' do
42
+ specify { expect(association.scope).to be_a ActiveRecord::Relation }
43
+ specify { expect(association.scope).to respond_to(:where) }
44
+ specify { expect(association.scope).to respond_to(:name_starts_with_a) }
45
+ end
46
+
47
+ describe '#target' do
48
+ specify { expect(association.target).to eq([]) }
49
+ specify { expect(existing_association.target).to eq(existing_book.authors) }
50
+ specify { expect { association.concat author }.to change { association.target.count }.to(1) }
51
+ end
52
+
53
+ describe '#default' do
54
+ before { Book.references_many :authors, default: ->(_book) { author.id } }
55
+ let(:existing_book) { Book.instantiate title: 'Genesis' }
56
+
57
+ specify { expect(association.target).to eq([author]) }
58
+ specify { expect { association.replace([other]) }.to change { association.target }.to([other]) }
59
+ specify { expect { association.replace([]) }.to change { association.target }.to eq([]) }
60
+
61
+ specify { expect(existing_association.target).to eq([]) }
62
+ specify { expect { existing_association.replace([other]) }.to change { existing_association.target }.to([other]) }
63
+ specify { expect { existing_association.replace([]) }.not_to change { existing_association.target } }
64
+ end
65
+
66
+ describe '#loaded?' do
67
+ specify { expect(association.loaded?).to eq(false) }
68
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
69
+ specify { expect { association.replace([]) }.to change { association.loaded? }.to(true) }
70
+ specify { expect { existing_association.replace([]) }.to change { existing_association.loaded? }.to(true) }
71
+ end
72
+
73
+ describe '#reload' do
74
+ specify { expect(association.reload).to eq([]) }
75
+
76
+ specify { expect(existing_association.reload).to eq(existing_book.authors) }
77
+
78
+ context do
79
+ before { existing_association.reader.last.name = 'Conan' }
80
+ specify do
81
+ expect { existing_association.reload }
82
+ .to change { existing_association.reader.map(&:name) }
83
+ .from(['Conan']).to(['Rick'])
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#reader' do
89
+ specify { expect(association.reader).to eq([]) }
90
+ specify { expect(association.reader).to be_a Granite::Form::Model::Associations::PersistenceAdapters::ActiveRecord::ReferencedProxy }
91
+
92
+ specify { expect(existing_association.reader.first).to be_a Author }
93
+ specify { expect(existing_association.reader.first).to be_persisted }
94
+
95
+ context do
96
+ before { association.concat author }
97
+ specify { expect(association.reader.last).to be_a Author }
98
+ specify { expect(association.reader.size).to eq(1) }
99
+ specify { expect(association.reader(true)).to eq([author]) }
100
+ end
101
+
102
+ context do
103
+ before { existing_association.concat other }
104
+ specify { expect(existing_association.reader.size).to eq(2) }
105
+ specify { expect(existing_association.reader.last.name).to eq('Ben') }
106
+ specify { expect(existing_association.reader(true).size).to eq(2) }
107
+ specify { expect(existing_association.reader(true).last.name).to eq('Ben') }
108
+ end
109
+
110
+ context 'proxy missing method delection' do
111
+ specify { expect(existing_association.reader).to respond_to(:where) }
112
+ specify { expect(existing_association.reader).to respond_to(:name_starts_with_a) }
113
+ end
114
+ end
115
+
116
+ describe '#writer' do
117
+ let(:new_author1) { Author.create!(name: 'John') }
118
+ let(:new_author2) { Author.create!(name: 'Adam') }
119
+ let(:new_author3) { Author.new(name: 'Jane') }
120
+
121
+ specify do
122
+ expect { association.writer([Dummy.new]) }
123
+ .to raise_error Granite::Form::AssociationTypeMismatch
124
+ end
125
+
126
+ specify { expect { association.writer(nil) }.to raise_error NoMethodError }
127
+ specify { expect { association.writer(new_author1) }.to raise_error NoMethodError }
128
+ specify { expect(association.writer([])).to eq([]) }
129
+
130
+ specify { expect(association.writer([new_author1])).to eq([new_author1]) }
131
+ specify do
132
+ expect { association.writer([new_author1]) }
133
+ .to change { association.reader.map(&:name) }.from([]).to(['John'])
134
+ end
135
+ specify do
136
+ expect { association.writer([new_author1]) }
137
+ .to change { book.read_attribute(:author_ids) }
138
+ .from([]).to([new_author1.id])
139
+ end
140
+
141
+ specify do
142
+ expect { existing_association.writer([new_author1, Dummy.new, new_author2]) }
143
+ .to raise_error Granite::Form::AssociationTypeMismatch
144
+ end
145
+ specify do
146
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer([new_author1, Dummy.new, new_author2]) } }
147
+ .not_to change { existing_book.read_attribute(:author_ids) }
148
+ end
149
+ specify do
150
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.writer([new_author1, Dummy.new, new_author2]) } }
151
+ .not_to change { existing_association.reader }
152
+ end
153
+
154
+ specify { expect { existing_association.writer(nil) }.to raise_error NoMethodError }
155
+ specify do
156
+ expect { muffle(NoMethodError) { existing_association.writer(nil) } }
157
+ .not_to change { existing_book.read_attribute(:author_ids) }
158
+ end
159
+ specify do
160
+ expect { muffle(NoMethodError) { existing_association.writer(nil) } }
161
+ .not_to change { existing_association.reader }
162
+ end
163
+
164
+ specify { expect(existing_association.writer([])).to eq([]) }
165
+ specify do
166
+ expect { existing_association.writer([]) }
167
+ .to change { existing_book.read_attribute(:author_ids) }.to([])
168
+ end
169
+ specify do
170
+ expect { existing_association.writer([]) }
171
+ .to change { existing_association.reader }.from([author]).to([])
172
+ end
173
+
174
+ specify { expect(existing_association.writer([new_author1, new_author2])).to eq([new_author1, new_author2]) }
175
+ specify do
176
+ expect { existing_association.writer([new_author1, new_author2]) }
177
+ .to change { existing_association.reader.map(&:name) }
178
+ .from(['Rick']).to(%w[John Adam])
179
+ end
180
+ specify do
181
+ expect { existing_association.writer([new_author1, new_author2]) }
182
+ .to change { existing_book.read_attribute(:author_ids) }
183
+ .from([author.id]).to([new_author1.id, new_author2.id])
184
+ end
185
+
186
+ specify do
187
+ expect { existing_association.writer([new_author3]) }
188
+ .to change { existing_association.target }.from([author]).to([new_author3])
189
+ end
190
+ specify do
191
+ expect { existing_association.writer([new_author3]) }
192
+ .to change { existing_book.read_attribute(:author_ids) }
193
+ .from([author.id]).to([nil])
194
+ end
195
+ end
196
+
197
+ describe '#concat' do
198
+ let(:new_author1) { Author.create!(name: 'John') }
199
+ let(:new_author2) { Author.create!(name: 'Adam') }
200
+
201
+ specify do
202
+ expect { association.concat(Dummy.new) }
203
+ .to raise_error Granite::Form::AssociationTypeMismatch
204
+ end
205
+
206
+ specify { expect { association.concat(nil) }.to raise_error Granite::Form::AssociationTypeMismatch }
207
+ specify { expect(association.concat([])).to eq([]) }
208
+ specify { expect(existing_association.concat([])).to eq(existing_book.authors) }
209
+ specify { expect(existing_association.concat).to eq(existing_book.authors) }
210
+
211
+ specify { expect(association.concat(new_author1)).to eq([new_author1]) }
212
+ specify do
213
+ expect { association.concat(new_author1) }
214
+ .to change { association.reader.map(&:name) }.from([]).to(['John'])
215
+ end
216
+ specify do
217
+ expect { association.concat(new_author1) }
218
+ .to change { book.read_attribute(:author_ids) }.from([]).to([new_author1.id])
219
+ end
220
+
221
+ specify do
222
+ expect { existing_association.concat(new_author1, Dummy.new, new_author2) }
223
+ .to raise_error Granite::Form::AssociationTypeMismatch
224
+ end
225
+ specify do
226
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.concat(new_author1, Dummy.new, new_author2) } }
227
+ .to change { existing_book.read_attribute(:author_ids) }
228
+ .from([author.id]).to([author.id, new_author1.id])
229
+ end
230
+ specify do
231
+ expect { muffle(Granite::Form::AssociationTypeMismatch) { existing_association.concat(new_author1, Dummy.new, new_author2) } }
232
+ .to change { existing_association.reader.map(&:name) }
233
+ .from(['Rick']).to(%w[Rick John])
234
+ end
235
+
236
+ specify do
237
+ expect(existing_association.concat(new_author1, new_author2))
238
+ .to eq([author, new_author1, new_author2])
239
+ end
240
+ specify do
241
+ expect { existing_association.concat([new_author1, new_author2]) }
242
+ .to change { existing_association.reader.map(&:name) }
243
+ .from(['Rick']).to(%w[Rick John Adam])
244
+ end
245
+ specify do
246
+ expect { existing_association.concat([new_author1, new_author2]) }
247
+ .to change { existing_book.read_attribute(:author_ids) }
248
+ .from([author.id]).to([author.id, new_author1.id, new_author2.id])
249
+ end
250
+ end
251
+ end