granite-form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,356 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Lifecycle do
4
+ context do
5
+ before do
6
+ stub_model(:user) do
7
+ include Granite::Form::Model::Lifecycle
8
+ end
9
+ end
10
+
11
+ subject { User.new }
12
+
13
+ %i[save create update destroy].each do |action|
14
+ specify { expect { subject.public_send "_#{action}_performer=", '' }.to raise_error NoMethodError }
15
+ end
16
+
17
+ context 'performer execution' do
18
+ let(:foo) { true }
19
+
20
+ specify { expect { subject.save { foo } }.to raise_error NameError }
21
+ specify { expect { subject.save { |_| attributes } }.to raise_error NameError }
22
+ specify { expect(subject.save { attributes }).to eq(true) }
23
+ specify { expect(subject.save { |_| foo }).to eq(true) }
24
+ end
25
+
26
+ context 'save' do
27
+ specify { expect { subject.save }.to raise_error Granite::Form::UnsavableObject }
28
+ specify { expect { subject.save! }.to raise_error Granite::Form::UnsavableObject }
29
+
30
+ specify { expect(subject.save { true }).to eq(true) }
31
+ specify { expect(subject.save! { true }).to eq(true) }
32
+
33
+ context 'instance performer' do
34
+ before { subject.define_save { false } }
35
+
36
+ specify { expect(subject.save).to eq(false) }
37
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
38
+ end
39
+
40
+ context 'create performer' do
41
+ before { User.define_create { true } }
42
+
43
+ specify { expect(subject.save).to eq(true) }
44
+ specify { expect(subject.save!).to eq(true) }
45
+
46
+ context do
47
+ subject { User.create }
48
+
49
+ specify { expect { subject.save }.to raise_error Granite::Form::UnsavableObject }
50
+ specify { expect { subject.save! }.to raise_error Granite::Form::UnsavableObject }
51
+ end
52
+
53
+ context 'instance performer' do
54
+ before { subject.define_create { false } }
55
+
56
+ specify { expect(subject.save).to eq(false) }
57
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
58
+ end
59
+ end
60
+
61
+ context 'update performer' do
62
+ before { User.define_update { true } }
63
+
64
+ specify { expect { subject.save }.to raise_error Granite::Form::UnsavableObject }
65
+ specify { expect { subject.save! }.to raise_error Granite::Form::UnsavableObject }
66
+
67
+ context do
68
+ subject { User.new.tap { |u| u.save { true } } }
69
+
70
+ specify { expect(subject.save).to eq(true) }
71
+ specify { expect(subject.save!).to eq(true) }
72
+
73
+ context 'instance performer' do
74
+ before { subject.define_update { false } }
75
+
76
+ specify { expect(subject.save).to eq(false) }
77
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'performers execution' do
83
+ before do
84
+ stub_model(:user) do
85
+ include Granite::Form::Model::Lifecycle
86
+
87
+ attribute :actions, Array, default: []
88
+
89
+ def append(action)
90
+ self.actions = actions + [action]
91
+ end
92
+
93
+ define_create { append :create }
94
+ define_update { append :update }
95
+ define_destroy { append :destroy }
96
+ end
97
+ end
98
+
99
+ subject { User.new }
100
+
101
+ specify do
102
+ subject.destroy
103
+ subject.save
104
+ subject.save
105
+ subject.destroy
106
+ subject.destroy
107
+ subject.save
108
+ expect(subject.actions).to eq(%i[destroy create update destroy destroy create])
109
+ end
110
+ end
111
+ end
112
+
113
+ context 'destroy' do
114
+ specify { expect { subject.destroy }.to raise_error Granite::Form::UndestroyableObject }
115
+ specify { expect { subject.destroy! }.to raise_error Granite::Form::UndestroyableObject }
116
+
117
+ specify { expect(subject.destroy { true }).to be_a User }
118
+ specify { expect(subject.destroy! { true }).to be_a User }
119
+
120
+ context 'instance performer' do
121
+ before { subject.define_save { false } }
122
+
123
+ specify { expect(subject.save).to eq(false) }
124
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
125
+ end
126
+ end
127
+ end
128
+
129
+ context do
130
+ before do
131
+ stub_class(:storage) do
132
+ def self.storage
133
+ @storage ||= {}
134
+ end
135
+ end
136
+ end
137
+
138
+ before do
139
+ stub_model(:user) do
140
+ include Granite::Form::Model::Lifecycle
141
+ delegate :generate_id, to: 'self.class'
142
+
143
+ attribute :id, Integer, &:generate_id
144
+ attribute :name, String
145
+ validates :id, :name, presence: true
146
+
147
+ def self.generate_id
148
+ @id = @id.to_i.next
149
+ end
150
+
151
+ define_save do
152
+ Storage.storage.merge!(id => attributes.symbolize_keys)
153
+ end
154
+
155
+ define_destroy do
156
+ Storage.storage.delete(id)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '.create' do
162
+ subject { User.create(name: 'Jonny') }
163
+
164
+ it { is_expected.to be_a User }
165
+ it { is_expected.to be_valid }
166
+ it { is_expected.to be_persisted }
167
+
168
+ context 'invalid' do
169
+ subject { User.create }
170
+
171
+ it { is_expected.to be_a User }
172
+ it { is_expected.to be_invalid }
173
+ it { is_expected.not_to be_persisted }
174
+ end
175
+ end
176
+
177
+ describe '.create!' do
178
+ subject { User.create!(name: 'Jonny') }
179
+
180
+ it { is_expected.to be_a User }
181
+ it { is_expected.to be_valid }
182
+ it { is_expected.to be_persisted }
183
+
184
+ context 'invalid' do
185
+ subject { User.create! }
186
+
187
+ specify { expect { subject }.to raise_error Granite::Form::ValidationError }
188
+ end
189
+ end
190
+
191
+ describe '#update, #update!' do
192
+ subject { User.new }
193
+
194
+ specify { expect { subject.update(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
195
+ specify { expect { subject.update!(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
196
+
197
+ specify { expect { subject.update({}) }.not_to change { subject.persisted? } }
198
+ specify { expect { subject.update!({}) }.to raise_error Granite::Form::ValidationError }
199
+
200
+ specify do
201
+ expect { subject.update(name: 'Jonny') }
202
+ .to change { Storage.storage.keys }.from([]).to([subject.id])
203
+ end
204
+ specify do
205
+ expect { subject.update!(name: 'Jonny') }
206
+ .to change { Storage.storage.keys }.from([]).to([subject.id])
207
+ end
208
+ end
209
+
210
+ describe '#update_attributes, #update_attributes!' do
211
+ subject { User.new }
212
+
213
+ specify { expect { subject.update_attributes(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
214
+ specify { expect { subject.update_attributes!(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
215
+
216
+ specify { expect { subject.update_attributes({}) }.not_to change { subject.persisted? } }
217
+ specify { expect { subject.update_attributes!({}) }.to raise_error Granite::Form::ValidationError }
218
+
219
+ specify do
220
+ expect { subject.update_attributes(name: 'Jonny') }
221
+ .to change { Storage.storage.keys }.from([]).to([subject.id])
222
+ end
223
+ specify do
224
+ expect { subject.update_attributes!(name: 'Jonny') }
225
+ .to change { Storage.storage.keys }.from([]).to([subject.id])
226
+ end
227
+ end
228
+
229
+ describe '#save, #save!' do
230
+ context 'invalid' do
231
+ subject { User.new }
232
+
233
+ it { is_expected.to be_invalid }
234
+ it { is_expected.not_to be_persisted }
235
+
236
+ specify { expect(subject.save).to eq(false) }
237
+ specify { expect { subject.save! }.to raise_error Granite::Form::ValidationError }
238
+
239
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
240
+ specify do
241
+ expect { muffle(Granite::Form::ValidationError) { subject.save! } }
242
+ .not_to change { subject.persisted? }
243
+ end
244
+ end
245
+
246
+ context 'create' do
247
+ subject { User.new(name: 'Jonny') }
248
+
249
+ it { is_expected.to be_valid }
250
+ it { is_expected.not_to be_persisted }
251
+
252
+ specify { expect(subject.save).to eq(true) }
253
+ specify { expect(subject.save!).to eq(true) }
254
+
255
+ specify { expect { subject.save }.to change { subject.persisted? }.from(false).to(true) }
256
+ specify { expect { subject.save! }.to change { subject.persisted? }.from(false).to(true) }
257
+
258
+ specify { expect { subject.save }.to change { Storage.storage.keys }.from([]).to([subject.id]) }
259
+ specify { expect { subject.save! }.to change { Storage.storage.keys }.from([]).to([subject.id]) }
260
+
261
+ context 'save failed' do
262
+ before { User.define_save { false } }
263
+
264
+ it { is_expected.not_to be_persisted }
265
+
266
+ specify { expect(subject.save).to eq(false) }
267
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
268
+
269
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
270
+ specify do
271
+ expect { muffle(Granite::Form::ObjectNotSaved) { subject.save! } }
272
+ .not_to change { subject.persisted? }
273
+ end
274
+ end
275
+ end
276
+
277
+ context 'update' do
278
+ subject! { User.new(name: 'Jonny').tap(&:save).tap { |u| u.name = 'Jimmy' } }
279
+
280
+ it { is_expected.to be_valid }
281
+ it { is_expected.to be_persisted }
282
+
283
+ specify { expect(subject.save).to eq(true) }
284
+ specify { expect(subject.save!).to eq(true) }
285
+
286
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
287
+ specify { expect { subject.save! }.not_to change { subject.persisted? } }
288
+
289
+ specify do
290
+ expect { subject.save }.to change { Storage.storage[subject.id] }
291
+ .from(hash_including(name: 'Jonny')).to(hash_including(name: 'Jimmy'))
292
+ end
293
+ specify do
294
+ expect { subject.save! }.to change { Storage.storage[subject.id] }
295
+ .from(hash_including(name: 'Jonny')).to(hash_including(name: 'Jimmy'))
296
+ end
297
+
298
+ context 'save failed' do
299
+ before { User.define_save { false } }
300
+
301
+ it { is_expected.to be_persisted }
302
+
303
+ specify { expect(subject.save).to eq(false) }
304
+ specify { expect { subject.save! }.to raise_error Granite::Form::ObjectNotSaved }
305
+
306
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
307
+ specify do
308
+ expect { muffle(Granite::Form::ObjectNotSaved) { subject.save! } }
309
+ .not_to change { subject.persisted? }
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ describe '#destroy, #destroy!' do
316
+ subject { User.create(name: 'Jonny') }
317
+
318
+ it { is_expected.to be_valid }
319
+ it { is_expected.to be_persisted }
320
+ it { is_expected.not_to be_destroyed }
321
+
322
+ specify { expect(subject.destroy).to eq(subject) }
323
+ specify { expect(subject.destroy!).to eq(subject) }
324
+
325
+ specify { expect { subject.destroy }.to change { subject.persisted? }.from(true).to(false) }
326
+ specify { expect { subject.destroy! }.to change { subject.persisted? }.from(true).to(false) }
327
+
328
+ specify { expect { subject.destroy }.to change { subject.destroyed? }.from(false).to(true) }
329
+ specify { expect { subject.destroy! }.to change { subject.destroyed? }.from(false).to(true) }
330
+
331
+ specify { expect { subject.destroy }.to change { Storage.storage.keys }.from([subject.id]).to([]) }
332
+ specify { expect { subject.destroy! }.to change { Storage.storage.keys }.from([subject.id]).to([]) }
333
+
334
+ context 'save failed' do
335
+ before { User.define_destroy { false } }
336
+
337
+ it { is_expected.to be_persisted }
338
+
339
+ specify { expect(subject.destroy).to eq(subject) }
340
+ specify { expect { subject.destroy! }.to raise_error Granite::Form::ObjectNotDestroyed }
341
+
342
+ specify { expect { subject.destroy }.not_to change { subject.persisted? } }
343
+ specify do
344
+ expect { muffle(Granite::Form::ObjectNotDestroyed) { subject.destroy! } }
345
+ .not_to change { subject.persisted? }
346
+ end
347
+
348
+ specify { expect { subject.destroy }.not_to change { subject.destroyed? } }
349
+ specify do
350
+ expect { muffle(Granite::Form::ObjectNotDestroyed) { subject.destroy! } }
351
+ .not_to change { subject.destroyed? }
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Persistence do
4
+ let(:model) do
5
+ stub_model do
6
+ include Granite::Form::Model::Persistence
7
+
8
+ attribute :name
9
+ attribute :count, default: 0
10
+ end
11
+ end
12
+
13
+ specify { expect(model.new).not_to be_persisted }
14
+ specify { expect(model.new).not_to be_destroyed }
15
+
16
+ describe '#instantiate' do
17
+ specify { expect(model.instantiate({})).to be_an_instance_of model }
18
+ specify { expect(model.instantiate({})).to be_persisted }
19
+ specify { expect(model.instantiate({})).not_to be_destroyed }
20
+
21
+ context do
22
+ subject(:instance) { model.instantiate(name: 'Hello', foo: 'Bar') }
23
+
24
+ specify { expect(subject.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
25
+ end
26
+ end
27
+
28
+ describe '#instantiate_collection' do
29
+ context do
30
+ subject(:instances) { model.instantiate_collection(name: 'Hello', foo: 'Bar') }
31
+
32
+ specify { expect(subject).to be_a Array }
33
+ specify { expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
34
+ end
35
+
36
+ context do
37
+ before { model.send(:include, Granite::Form::Model::Scopes) }
38
+ subject(:instances) { model.instantiate_collection([{name: 'Hello', foo: 'Bar'}, {name: 'World'}]) }
39
+
40
+ specify { expect(subject).to be_a Granite::Form::Model::Scopes::ScopeProxy }
41
+ specify { expect(subject.count).to eq(2) }
42
+ specify { expect(subject.first.instance_variable_get(:@initial_attributes)).to eq({name: 'Hello'}.stringify_keys) }
43
+ specify { expect(subject.second.instance_variable_get(:@initial_attributes)).to eq({name: 'World'}.stringify_keys) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Primary do
4
+ context 'undefined' do
5
+ let(:model) do
6
+ stub_model do
7
+ include Granite::Form::Model::Primary
8
+
9
+ attribute :name, String
10
+ end
11
+ end
12
+
13
+ specify { expect(model.has_primary_attribute?).to eq(false) }
14
+ specify { expect(model.new.has_primary_attribute?).to eq(false) }
15
+ specify { expect { model.new.primary_attribute }.to raise_error NoMethodError }
16
+ specify { expect(model.new(name: 'Hello')).to eq(model.new(name: 'Hello')) }
17
+ specify { expect(model.new(name: 'Hello')).to eql(model.new(name: 'Hello')) }
18
+
19
+ context do
20
+ let(:object) { model.new(name: 'Hello') }
21
+ specify { expect(object).not_to eq(object.clone.tap { |o| o.update(name: 'World') }) }
22
+ specify { expect(object).not_to eql(object.clone.tap { |o| o.update(name: 'World') }) }
23
+ end
24
+ end
25
+
26
+ context 'defined' do
27
+ let(:model) do
28
+ stub_model do
29
+ include Granite::Form::Model::Primary
30
+
31
+ primary_attribute
32
+ attribute :name, String
33
+ end
34
+ end
35
+
36
+ specify { expect(model.has_primary_attribute?).to eq(true) }
37
+ specify { expect(model.new.has_primary_attribute?).to eq(true) }
38
+ specify { expect(model.new.primary_attribute).to be_a(Granite::Form::UUID) }
39
+ specify { expect(model.new(name: 'Hello')).not_to eq(model.new(name: 'Hello')) }
40
+ specify { expect(model.new(name: 'Hello')).not_to eql(model.new(name: 'Hello')) }
41
+ specify { expect(model.new(id: 0).id).not_to eq(Granite::Form::UUID.parse_int(0)) }
42
+ specify { expect(model.new.tap { |o| o.id = 0 }.id).to eq(Granite::Form::UUID.parse_int(0)) }
43
+
44
+ context do
45
+ let(:object) { model.new(name: 'Hello') }
46
+ specify { expect(object).to eq(object.clone.tap { |o| o.update(name: 'World') }) }
47
+ specify { expect(object).to eql(object.clone.tap { |o| o.update(name: 'World') }) }
48
+ end
49
+ end
50
+
51
+ context 'defined' do
52
+ let(:model) do
53
+ stub_model do
54
+ include Granite::Form::Model::Primary
55
+
56
+ primary_attribute type: Integer
57
+ attribute :name
58
+ end
59
+ end
60
+
61
+ specify { expect(model.has_primary_attribute?).to eq(true) }
62
+ specify { expect(model.new.has_primary_attribute?).to eq(true) }
63
+ specify { expect(model.new.primary_attribute).to be_nil }
64
+ specify { expect(model.new(name: 'Hello')).not_to eq(model.new(name: 'Hello')) }
65
+ specify { expect(model.new(name: 'Hello')).not_to eql(model.new(name: 'Hello')) }
66
+ specify { expect(model.new(name: 'Hello').tap { |o| o.id = 1 }).not_to eq(model.new(name: 'Hello').tap { |o| o.id = 2 }) }
67
+ specify { expect(model.new(name: 'Hello').tap { |o| o.id = 1 }).not_to eql(model.new(name: 'Hello').tap { |o| o.id = 2 }) }
68
+ specify { expect(model.new(id: 1).id).to be_nil }
69
+ specify { expect(model.new.tap { |o| o.assign_attributes(id: 1) }.id).to be_nil }
70
+ specify { expect(model.new.tap { |o| o.id = 1 }.id).to eq(1) }
71
+
72
+ context do
73
+ let(:object) { model.new(name: 'Hello').tap { |o| o.id = 1 } }
74
+ specify { expect(object).to eq(object.clone.tap { |o| o.update(name: 'World') }) }
75
+ specify { expect(object).to eql(object.clone.tap { |o| o.update(name: 'World') }) }
76
+ end
77
+
78
+ context do
79
+ let(:object) { model.new(name: 'Hello') }
80
+ specify { expect(object).to eq(object) }
81
+ specify { expect(object).to eql(object) }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Representation do
4
+ context 'integration' do
5
+ before do
6
+ stub_model(:author) do
7
+ attribute :rate, Integer
8
+ end
9
+
10
+ stub_model(:post) do
11
+ include Granite::Form::Model::Representation
12
+
13
+ attribute :author, Object
14
+ alias_attribute :a, :author
15
+ represents :rate, of: :a
16
+ alias_attribute :r, :rate
17
+ end
18
+ end
19
+ let(:author) { Author.new(rate: '42') }
20
+
21
+ specify { expect(Post.reflect_on_attribute(:rate).reference).to eq('author') }
22
+
23
+ specify { expect(Post.new(author: author).rate).to eq(42) }
24
+ specify { expect(Post.new(author: author).rate_before_type_cast).to eq('42') }
25
+ specify { expect(Post.new(rate: '33', author: author).rate).to eq(33) }
26
+ specify { expect(Post.new(rate: '33', author: author).author.rate).to eq(33) }
27
+ specify { expect(Post.new(r: '33', author: author).rate).to eq(33) }
28
+ specify { expect(Post.new(r: '33', author: author).author.rate).to eq(33) }
29
+ specify { expect(Post.new(author: author).rate?).to eq(true) }
30
+ specify { expect(Post.new(rate: nil, author: author).rate?).to eq(false) }
31
+
32
+ specify { expect(Post.new.rate).to be_nil }
33
+ specify { expect(Post.new.rate_before_type_cast).to be_nil }
34
+ specify { expect { Post.new(rate: '33') }.to raise_error(NoMethodError) }
35
+
36
+ context 'ActionController::Parameters' do
37
+ let(:params) { instance_double('ActionController::Parameters', to_unsafe_hash: {rate: '33', author: author}) }
38
+ specify { expect { Post.new(params) }.not_to raise_error }
39
+ end
40
+
41
+ context 'dirty' do
42
+ before do
43
+ Author.include Granite::Form::Model::Dirty
44
+ Post.include Granite::Form::Model::Dirty
45
+ end
46
+
47
+ specify do
48
+ expect(Post.new(author: author, rate: '33').changes)
49
+ .to eq('author' => [nil, author], 'rate' => [42, 33])
50
+
51
+ expect(Post.new(author: author, rate: '33').changes)
52
+ .to eq('author' => [nil, author])
53
+ end
54
+ end
55
+
56
+ context do
57
+ before do
58
+ stub_class(:author, ActiveRecord::Base)
59
+
60
+ stub_model(:post) do
61
+ include Granite::Form::Model::Associations
62
+ include Granite::Form::Model::Representation
63
+
64
+ references_one :author
65
+ alias_association :a, :author
66
+ represents :name, of: :a
67
+ end
68
+ end
69
+ let!(:author) { Author.create!(name: 42) }
70
+
71
+ specify { expect(Post.reflect_on_attribute(:name).reference).to eq('author') }
72
+
73
+ specify { expect(Post.new(name: '33', author: author).name).to eq('33') }
74
+ specify { expect(Post.new(name: '33', author: author).author.name).to eq('33') }
75
+ end
76
+
77
+ context 'multiple attributes in a single represents definition' do
78
+ before do
79
+ stub_model(:author) do
80
+ attribute :first_name, String
81
+ attribute :last_name, String
82
+ end
83
+
84
+ stub_model(:post) do
85
+ include Granite::Form::Model::Representation
86
+
87
+ attribute :author, Object
88
+ represents :first_name, :last_name, of: :author
89
+ end
90
+ end
91
+
92
+ let(:author) { Author.new(first_name: 'John', last_name: 'Doe') }
93
+ let(:post) { Post.new }
94
+
95
+ specify do
96
+ expect { post.update(author: author) }
97
+ .to change { post.first_name }.to('John')
98
+ .and change { post.last_name }.to('Doe')
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#validate' do
104
+ before do
105
+ stub_class(:author, ActiveRecord::Base) do
106
+ validates :name, presence: true
107
+
108
+ # Emulate Active Record association auto save error.
109
+ def errors
110
+ super.tap do |errors|
111
+ errors.add(:'user.email', 'is invalid') if errors[:'user.email'].empty?
112
+ end
113
+ end
114
+ end
115
+
116
+ stub_model(:post) do
117
+ include Granite::Form::Model::Associations
118
+ include Granite::Form::Model::Representation
119
+
120
+ references_one :author
121
+ represents :name, :email, of: :author
122
+ end
123
+ end
124
+
125
+ let(:post) { Post.new(author: Author.new) }
126
+
127
+ specify do
128
+ expect { post.validate }.to change { post.errors.messages }
129
+ .to(hash_including('author.user.email': ['is invalid'], name: ["can't be blank"]))
130
+ end
131
+
132
+ if ActiveModel.version >= Gem::Version.new('6.1.0')
133
+ specify do
134
+ expect { post.validate }.to change { post.errors.details }
135
+ .to(hash_including('author.user.email': [{error: 'is invalid'}], name: [{error: :blank}]))
136
+ end
137
+ end
138
+ end
139
+ end