active_data 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rspec +0 -1
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +13 -6
  6. data/Appraisals +7 -0
  7. data/Gemfile +1 -5
  8. data/Guardfile +68 -15
  9. data/README.md +144 -2
  10. data/active_data.gemspec +19 -11
  11. data/gemfiles/rails.4.0.gemfile +14 -0
  12. data/gemfiles/rails.4.1.gemfile +14 -0
  13. data/gemfiles/rails.4.2.gemfile +14 -0
  14. data/gemfiles/rails.5.0.gemfile +14 -0
  15. data/lib/active_data.rb +120 -3
  16. data/lib/active_data/active_record/associations.rb +50 -0
  17. data/lib/active_data/active_record/nested_attributes.rb +24 -0
  18. data/lib/active_data/config.rb +40 -0
  19. data/lib/active_data/errors.rb +93 -0
  20. data/lib/active_data/extensions.rb +33 -0
  21. data/lib/active_data/model.rb +16 -74
  22. data/lib/active_data/model/associations.rb +84 -15
  23. data/lib/active_data/model/associations/base.rb +79 -0
  24. data/lib/active_data/model/associations/collection/embedded.rb +12 -0
  25. data/lib/active_data/model/associations/collection/proxy.rb +32 -0
  26. data/lib/active_data/model/associations/collection/referenced.rb +26 -0
  27. data/lib/active_data/model/associations/embeds_many.rb +124 -18
  28. data/lib/active_data/model/associations/embeds_one.rb +90 -15
  29. data/lib/active_data/model/associations/nested_attributes.rb +180 -0
  30. data/lib/active_data/model/associations/references_many.rb +96 -0
  31. data/lib/active_data/model/associations/references_one.rb +83 -0
  32. data/lib/active_data/model/associations/reflections/base.rb +100 -0
  33. data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
  34. data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
  35. data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
  36. data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
  37. data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
  38. data/lib/active_data/model/associations/validations.rb +63 -0
  39. data/lib/active_data/model/attributes.rb +247 -0
  40. data/lib/active_data/model/attributes/attribute.rb +73 -0
  41. data/lib/active_data/model/attributes/base.rb +116 -0
  42. data/lib/active_data/model/attributes/collection.rb +17 -0
  43. data/lib/active_data/model/attributes/dictionary.rb +26 -0
  44. data/lib/active_data/model/attributes/localized.rb +42 -0
  45. data/lib/active_data/model/attributes/reference_many.rb +21 -0
  46. data/lib/active_data/model/attributes/reference_one.rb +42 -0
  47. data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
  48. data/lib/active_data/model/attributes/reflections/base.rb +62 -0
  49. data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
  50. data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
  51. data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
  52. data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
  53. data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
  54. data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
  55. data/lib/active_data/model/attributes/represents.rb +64 -0
  56. data/lib/active_data/model/callbacks.rb +71 -0
  57. data/lib/active_data/model/conventions.rb +35 -0
  58. data/lib/active_data/model/dirty.rb +77 -0
  59. data/lib/active_data/model/lifecycle.rb +307 -0
  60. data/lib/active_data/model/localization.rb +21 -0
  61. data/lib/active_data/model/persistence.rb +57 -0
  62. data/lib/active_data/model/primary.rb +51 -0
  63. data/lib/active_data/model/scopes.rb +77 -0
  64. data/lib/active_data/model/validations.rb +27 -0
  65. data/lib/active_data/model/validations/associated.rb +19 -0
  66. data/lib/active_data/model/validations/nested.rb +39 -0
  67. data/lib/active_data/railtie.rb +7 -0
  68. data/lib/active_data/version.rb +1 -1
  69. data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
  70. data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
  71. data/spec/lib/active_data/config_spec.rb +44 -0
  72. data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
  73. data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
  74. data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
  75. data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
  76. data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
  77. data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
  78. data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
  79. data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
  80. data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
  81. data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
  82. data/spec/lib/active_data/model/associations_spec.rb +189 -0
  83. data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
  84. data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
  85. data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
  86. data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
  87. data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
  88. data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
  89. data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
  90. data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
  91. data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
  92. data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
  93. data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
  94. data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
  95. data/spec/lib/active_data/model/attributes_spec.rb +243 -0
  96. data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
  97. data/spec/lib/active_data/model/conventions_spec.rb +12 -0
  98. data/spec/lib/active_data/model/dirty_spec.rb +75 -0
  99. data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
  100. data/spec/lib/active_data/model/nested_attributes.rb +202 -0
  101. data/spec/lib/active_data/model/persistence_spec.rb +47 -0
  102. data/spec/lib/active_data/model/primary_spec.rb +84 -0
  103. data/spec/lib/active_data/model/scopes_spec.rb +88 -0
  104. data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
  105. data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
  106. data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
  107. data/spec/lib/active_data/model/validations_spec.rb +31 -0
  108. data/spec/lib/active_data/model_spec.rb +1 -32
  109. data/spec/lib/active_data_spec.rb +12 -0
  110. data/spec/spec_helper.rb +39 -0
  111. data/spec/support/model_helpers.rb +10 -0
  112. metadata +246 -54
  113. data/gemfiles/Gemfile.rails-3 +0 -14
  114. data/lib/active_data/attributes/base.rb +0 -69
  115. data/lib/active_data/attributes/localized.rb +0 -42
  116. data/lib/active_data/model/associations/association.rb +0 -30
  117. data/lib/active_data/model/attributable.rb +0 -122
  118. data/lib/active_data/model/collectionizable.rb +0 -55
  119. data/lib/active_data/model/collectionizable/proxy.rb +0 -42
  120. data/lib/active_data/model/extensions.rb +0 -9
  121. data/lib/active_data/model/extensions/array.rb +0 -24
  122. data/lib/active_data/model/extensions/big_decimal.rb +0 -17
  123. data/lib/active_data/model/extensions/boolean.rb +0 -38
  124. data/lib/active_data/model/extensions/date.rb +0 -17
  125. data/lib/active_data/model/extensions/date_time.rb +0 -17
  126. data/lib/active_data/model/extensions/float.rb +0 -17
  127. data/lib/active_data/model/extensions/hash.rb +0 -22
  128. data/lib/active_data/model/extensions/integer.rb +0 -17
  129. data/lib/active_data/model/extensions/localized.rb +0 -22
  130. data/lib/active_data/model/extensions/object.rb +0 -17
  131. data/lib/active_data/model/extensions/string.rb +0 -17
  132. data/lib/active_data/model/extensions/time.rb +0 -17
  133. data/lib/active_data/model/localizable.rb +0 -31
  134. data/lib/active_data/model/nested_attributes.rb +0 -58
  135. data/lib/active_data/model/parameterizable.rb +0 -29
  136. data/lib/active_data/validations.rb +0 -7
  137. data/lib/active_data/validations/associated.rb +0 -17
  138. data/spec/lib/active_data/model/attributable_spec.rb +0 -191
  139. data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
  140. data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
  141. data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
  142. data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -1,57 +1,276 @@
1
1
  # encoding: UTF-8
2
2
  require 'spec_helper'
3
3
 
4
- describe ActiveData::Model::Associations::EmbedsMany do
4
+ describe ActiveData::Model::Associations::EmbedsOne do
5
+ before do
6
+ stub_model(:author) do
7
+ include ActiveData::Model::Lifecycle
5
8
 
6
- class OneAssoc
7
- include ActiveData::Model
9
+ attribute :name, String
10
+ validates :name, presence: true
11
+ end
12
+
13
+ stub_model(:book) do
14
+ include ActiveData::Model::Persistence
15
+ include ActiveData::Model::Associations
8
16
 
9
- attribute :name
17
+ attribute :title, String
18
+ embeds_one :author
19
+ end
10
20
  end
11
21
 
12
- let(:klass) do
13
- Class.new do
14
- include ActiveData::Model
22
+ let(:book) { Book.new }
23
+ let(:association) { book.association(:author) }
15
24
 
16
- attribute :name
17
- embeds_one :one_assoc
18
- end
25
+ let(:existing_book) { Book.instantiate title: 'My Life', author: {'name' => 'Johny'} }
26
+ let(:existing_association) { existing_book.association(:author) }
27
+
28
+ describe 'book#association' do
29
+ specify { expect(association).to be_a described_class }
30
+ specify { expect(association).to eq(book.association(:author)) }
19
31
  end
20
32
 
21
- subject { klass.new(name: 'world') }
33
+ describe '#build' do
34
+ specify { expect(association.build).to be_a Author }
35
+ specify { expect(association.build).not_to be_persisted }
36
+
37
+ specify { expect { association.build(name: 'Fred') }
38
+ .not_to change { book.read_attribute(:author) } }
39
+
40
+ specify { expect { existing_association.build(name: 'Fred') }
41
+ .not_to change { existing_book.read_attribute(:author) } }
42
+ end
43
+
44
+ describe '#create' do
45
+ specify { expect(association.create).to be_a Author }
46
+ specify { expect(association.create).not_to be_persisted }
47
+
48
+ specify { expect(association.create(name: 'Fred')).to be_a Author }
49
+ specify { expect(association.create(name: 'Fred')).to be_persisted }
50
+
51
+ specify { expect { association.create }
52
+ .not_to change { book.read_attribute(:author) } }
53
+ specify { expect { association.create(name: 'Fred') }
54
+ .to change { book.read_attribute(:author) }.from(nil).to('name' => 'Fred') }
55
+
56
+ specify { expect { existing_association.create }
57
+ .not_to change { existing_book.read_attribute(:author) } }
58
+ specify { expect { existing_association.create(name: 'Fred') }
59
+ .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to('name' => 'Fred') }
60
+ end
61
+
62
+ describe '#create!' do
63
+ specify { expect { association.create! }.to raise_error ActiveData::ValidationError }
64
+
65
+ specify { expect(association.create!(name: 'Fred')).to be_a Author }
66
+ specify { expect(association.create!(name: 'Fred')).to be_persisted }
22
67
 
23
- its(:one_assoc) { should be_nil }
68
+ specify { expect { association.create! rescue nil }
69
+ .not_to change { book.read_attribute(:author) } }
70
+ specify { expect { association.create! rescue nil }
71
+ .to change { association.reader.try(:attributes) }.from(nil).to('name' => nil) }
72
+ specify { expect { association.create(name: 'Fred') }
73
+ .to change { book.read_attribute(:author) }.from(nil).to('name' => 'Fred') }
24
74
 
25
- context 'accessor with objects' do
26
- before { subject.one_assoc = OneAssoc.new(name: 'foo') }
27
- specify { subject.one_assoc.should be_instance_of OneAssoc }
28
- specify { subject.one_assoc.name.should == 'foo' }
75
+ specify { expect { existing_association.create! rescue nil }
76
+ .not_to change { existing_book.read_attribute(:author) } }
77
+ specify { expect { existing_association.create! rescue nil }
78
+ .to change { existing_association.reader.try(:attributes) }.from('name' => 'Johny').to('name' => nil) }
79
+ specify { expect { existing_association.create!(name: 'Fred') }
80
+ .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to('name' => 'Fred') }
29
81
  end
30
82
 
31
- context 'accessor with attributes' do
32
- before { subject.one_assoc = { name: 'foo' } }
33
- specify { subject.one_assoc.should be_instance_of OneAssoc }
34
- specify { subject.one_assoc.name.should == 'foo' }
83
+ describe '#apply_changes' do
84
+ specify { expect { association.build; association.apply_changes }.to change { association.target.try(:persisted?) }.to(false) }
85
+ specify { expect { association.build(name: 'Fred'); association.apply_changes }.to change { association.target.try(:persisted?) }.to(true) }
86
+ specify { expect { existing_association.target.mark_for_destruction; existing_association.apply_changes }.to change { existing_association.target }.to(nil) }
87
+ specify { expect { existing_association.target.destroy!; existing_association.apply_changes }.to change { existing_association.target }.to(nil) }
88
+ specify { expect { existing_association.target.mark_for_destruction; existing_association.apply_changes }.to change { existing_association.destroyed.try(:name) }.from(nil).to('Johny') }
89
+ specify { expect { existing_association.target.destroy!; existing_association.apply_changes }.to change { existing_association.destroyed.try(:name) }.from(nil).to('Johny') }
35
90
  end
36
91
 
37
- context 'accessor with nothing' do
38
- before { subject.one_assoc = nil }
39
- specify { subject.one_assoc.should be_nil }
92
+ describe '#apply_changes!' do
93
+ specify { expect { association.build; association.apply_changes! }.to raise_error ActiveData::AssociationChangesNotApplied }
94
+ specify { expect { association.build(name: 'Fred'); association.apply_changes! }.to change { association.target.try(:persisted?) }.to(true) }
95
+ specify { expect { existing_association.target.mark_for_destruction; existing_association.apply_changes! }.to change { existing_association.target }.to(nil) }
96
+ specify { expect { existing_association.target.destroy!; existing_association.apply_changes! }.to change { existing_association.target }.to(nil) }
40
97
  end
41
98
 
42
- describe '#==' do
43
- let(:instance) { klass.new(name: 'world') }
44
- before { subject.one_assoc = { name: 'foo' } }
45
- specify { subject.should_not == instance }
99
+ describe '#target' do
100
+ specify { expect(association.target).to be_nil }
101
+ specify { expect(existing_association.target).to eq(existing_book.author) }
102
+ specify { expect { association.build }.to change { association.target }.to(an_instance_of(Author)) }
103
+ end
104
+
105
+ describe '#default' do
106
+ before { Book.embeds_one :author, default: -> { { name: 'Default' } } }
107
+ before do
108
+ Author.class_eval do
109
+ include ActiveData::Model::Primary
110
+ primary :name
111
+ end
112
+ end
113
+ let(:new_author) { Author.new.tap { |a| a.name = 'Morty' } }
114
+ let(:existing_book) { Book.instantiate title: 'My Life' }
115
+
116
+ specify { expect(association.target.name).to eq('Default') }
117
+ specify { expect(association.target.new_record?).to eq(true) }
118
+ specify { expect { association.replace(new_author) }.to change { association.target.name }.to eq('Morty') }
119
+ specify { expect { association.replace(nil) }.to change { association.target }.to be_nil }
120
+
121
+ specify { expect(existing_association.target).to be_nil }
122
+ specify { expect { existing_association.replace(new_author) }.to change { existing_association.target }.to(an_instance_of(Author)) }
123
+ specify { expect { existing_association.replace(nil) }.not_to change { existing_association.target } }
124
+
125
+ context do
126
+ before { Author.send(:include, ActiveData::Model::Dirty) }
127
+ specify { expect(association.target).not_to be_changed }
128
+ end
129
+ end
130
+
131
+ describe '#loaded?' do
132
+ let(:new_author) { Author.new(name: 'Morty') }
133
+
134
+ specify { expect(association.loaded?).to eq(false) }
135
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
136
+ specify { expect { association.build }.to change { association.loaded? }.to(true) }
137
+ specify { expect { association.replace(new_author) }.to change { association.loaded? }.to(true) }
138
+ specify { expect { association.replace(nil) }.to change { association.loaded? }.to(true) }
139
+ specify { expect { existing_association.replace(new_author) }.to change { existing_association.loaded? }.to(true) }
140
+ specify { expect { existing_association.replace(nil) }.to change { existing_association.loaded? }.to(true) }
141
+ end
142
+
143
+ describe '#reload' do
144
+ specify { expect(association.reload).to be_nil }
145
+
146
+ specify { expect(existing_association.reload).to be_a Author }
147
+ specify { expect(existing_association.reload).to be_persisted }
46
148
 
47
149
  context do
48
- before { instance.one_assoc = { name: 'foo1' } }
49
- specify { subject.should_not == instance }
150
+ before { association.build(name: 'Fred') }
151
+ specify { expect { association.reload }
152
+ .to change { association.reader.try(:attributes) }.from('name' => 'Fred').to(nil) }
50
153
  end
51
154
 
52
155
  context do
53
- before { instance.one_assoc = { name: 'foo' } }
54
- specify { subject.should == instance }
156
+ before { existing_association.build(name: 'Fred') }
157
+ specify { expect { existing_association.reload }
158
+ .to change { existing_association.reader.try(:attributes) }
159
+ .from('name' => 'Fred').to('name' => 'Johny') }
160
+ end
161
+ end
162
+
163
+ describe '#clear' do
164
+ specify { expect(association.clear).to eq(true) }
165
+ specify { expect { association.clear }.not_to change { association.reader } }
166
+
167
+ specify { expect(existing_association.clear).to eq(true) }
168
+ specify { expect { existing_association.clear }
169
+ .to change { existing_association.reader.try(:attributes) }.from('name' => 'Johny').to(nil) }
170
+ specify { expect { existing_association.clear }
171
+ .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to(nil) }
172
+
173
+ context do
174
+ before { Author.send(:include, ActiveData::Model::Callbacks) }
175
+ if ActiveModel.version >= Gem::Version.new('5.0.0')
176
+ before { Author.before_destroy { throw :abort } }
177
+ else
178
+ before { Author.before_destroy { false } }
179
+ end
180
+ specify { expect(existing_association.clear).to eq(false) }
181
+ specify { expect { existing_association.clear }
182
+ .not_to change { existing_association.reader } }
183
+ specify { expect { existing_association.clear }
184
+ .not_to change { existing_book.read_attribute(:author).symbolize_keys } }
185
+ end
186
+ end
187
+
188
+ describe '#reader' do
189
+ specify { expect(association.reader).to be_nil }
190
+
191
+ specify { expect(existing_association.reader).to be_a Author }
192
+ specify { expect(existing_association.reader).to be_persisted }
193
+
194
+ context do
195
+ before { association.build }
196
+ specify { expect(association.reader).to be_a Author }
197
+ specify { expect(association.reader).not_to be_persisted }
198
+ specify { expect(association.reader(true)).to be_nil }
199
+ end
200
+
201
+ context do
202
+ before { existing_association.build(name: 'Fred') }
203
+ specify { expect(existing_association.reader.name).to eq('Fred') }
204
+ specify { expect(existing_association.reader(true).name).to eq('Johny') }
205
+ end
206
+ end
207
+
208
+ describe '#writer' do
209
+ let(:new_author) { Author.new(name: 'Morty') }
210
+ let(:invalid_author) { Author.new }
211
+
212
+ context 'new owner' do
213
+ let(:book) do
214
+ Book.new.tap do |book|
215
+ book.send(:mark_persisted!)
216
+ end
217
+ end
218
+
219
+ specify { expect { association.writer(nil) }
220
+ .not_to change { book.read_attribute(:author) } }
221
+ specify { expect { association.writer(new_author) }
222
+ .to change { association.reader.try(:attributes) }.from(nil).to('name' => 'Morty') }
223
+ specify { expect { association.writer(new_author) }
224
+ .to change { book.read_attribute(:author) }.from(nil).to('name' => 'Morty') }
225
+
226
+ specify { expect { association.writer(invalid_author) }
227
+ .to raise_error ActiveData::AssociationChangesNotApplied }
228
+ specify { expect { association.writer(invalid_author) rescue nil }
229
+ .not_to change { association.reader } }
230
+ specify { expect { association.writer(invalid_author) rescue nil }
231
+ .not_to change { book.read_attribute(:author) } }
232
+ end
233
+
234
+ context 'persisted owner' do
235
+ specify { expect { association.writer(stub_model(:dummy).new) }
236
+ .to raise_error ActiveData::AssociationTypeMismatch }
237
+
238
+ specify { expect(association.writer(nil)).to be_nil }
239
+ specify { expect(association.writer(new_author)).to eq(new_author) }
240
+ specify { expect { association.writer(nil) }
241
+ .not_to change { book.read_attribute(:author) } }
242
+ specify { expect { association.writer(new_author) }
243
+ .to change { association.reader.try(:attributes) }.from(nil).to('name' => 'Morty') }
244
+ specify { expect { association.writer(new_author) }
245
+ .not_to change { book.read_attribute(:author) } }
246
+
247
+ specify { expect { association.writer(invalid_author) }
248
+ .to change { association.reader.try(:attributes) }.from(nil).to('name' => nil) }
249
+ specify { expect { association.writer(invalid_author) }
250
+ .not_to change { book.read_attribute(:author) } }
251
+
252
+ specify { expect { existing_association.writer(stub_model(:dummy).new) rescue nil }
253
+ .not_to change { existing_book.read_attribute(:author) } }
254
+ specify { expect { existing_association.writer(stub_model(:dummy).new) rescue nil }
255
+ .not_to change { existing_association.reader } }
256
+
257
+ specify { expect(existing_association.writer(nil)).to be_nil }
258
+ specify { expect(existing_association.writer(new_author)).to eq(new_author) }
259
+ specify { expect { existing_association.writer(nil) }
260
+ .to change { existing_book.read_attribute(:author) }.from('name' => 'Johny').to(nil) }
261
+ specify { expect { existing_association.writer(new_author) }
262
+ .to change { existing_association.reader.try(:attributes) }
263
+ .from('name' => 'Johny').to('name' => 'Morty') }
264
+ specify { expect { existing_association.writer(new_author) }
265
+ .to change { existing_book.read_attribute(:author) }
266
+ .from('name' => 'Johny').to('name' => 'Morty') }
267
+
268
+ specify { expect { existing_association.writer(invalid_author) }
269
+ .to raise_error ActiveData::AssociationChangesNotApplied }
270
+ specify { expect { existing_association.writer(invalid_author) rescue nil }
271
+ .not_to change { existing_association.reader } }
272
+ specify { expect { existing_association.writer(invalid_author) rescue nil }
273
+ .not_to change { existing_book.read_attribute(:author) } }
55
274
  end
56
275
  end
57
276
  end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'lib/active_data/model/nested_attributes'
4
+
5
+ describe ActiveData::Model::Associations::NestedAttributes do
6
+ before do
7
+ stub_model :user do
8
+ include ActiveData::Model::Associations
9
+
10
+ attribute :email, String
11
+ embeds_one :profile
12
+ embeds_many :projects
13
+
14
+ accepts_nested_attributes_for :profile, :projects
15
+
16
+ def save
17
+ apply_association_changes!
18
+ end
19
+ end
20
+ end
21
+
22
+ include_examples 'nested attributes'
23
+ end
@@ -0,0 +1,196 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::ReferencesMany do
5
+ before do
6
+ stub_model(:dummy)
7
+ stub_class(:author, ActiveRecord::Base) do
8
+ scope :name_starts_with_a, -> { where('name LIKE "a%"') }
9
+ end
10
+
11
+ stub_model(:book) do
12
+ include ActiveData::Model::Persistence
13
+ include ActiveData::Model::Associations
14
+
15
+ attribute :title
16
+ references_many :authors
17
+ end
18
+ end
19
+
20
+ let(:author) { Author.create!(name: 'Rick') }
21
+ let(:other) { Author.create!(name: 'Ben') }
22
+
23
+ let(:book) { Book.new }
24
+ let(:association) { book.association(:authors) }
25
+
26
+ let(:existing_book) { Book.instantiate title: 'Genesis', author_ids: [author.id] }
27
+ let(:existing_association) { existing_book.association(:authors) }
28
+
29
+ describe 'book#association' do
30
+ specify { expect(association).to be_a described_class }
31
+ specify { expect(association).to eq(book.association(:authors)) }
32
+ end
33
+
34
+ describe '#scope' do
35
+ specify { expect(association.scope).to be_a ActiveRecord::Relation }
36
+ specify { expect(association.scope).to respond_to(:where) }
37
+ specify { expect(association.scope).to respond_to(:name_starts_with_a) }
38
+ end
39
+
40
+ describe '#target' do
41
+ specify { expect(association.target).to eq([]) }
42
+ specify { expect(existing_association.target).to eq(existing_book.authors) }
43
+ specify { expect { association.concat author }.to change { association.target.count }.to(1) }
44
+ end
45
+
46
+ describe '#default' do
47
+ before { Book.references_many :authors, default: ->(book) { author.id } }
48
+ let(:existing_book) { Book.instantiate title: 'Genesis' }
49
+
50
+ specify { expect(association.target).to eq([author]) }
51
+ specify { expect { association.replace([other]) }.to change { association.target }.to([other]) }
52
+ specify { expect { association.replace([]) }.to change { association.target }.to eq([]) }
53
+
54
+ specify { expect(existing_association.target).to eq([]) }
55
+ specify { expect { existing_association.replace([other]) }.to change { existing_association.target }.to([other]) }
56
+ specify { expect { existing_association.replace([]) }.not_to change { existing_association.target } }
57
+ end
58
+
59
+ describe '#loaded?' do
60
+ specify { expect(association.loaded?).to eq(false) }
61
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
62
+ specify { expect { association.replace([]) }.to change { association.loaded? }.to(true) }
63
+ specify { expect { existing_association.replace([]) }.to change { existing_association.loaded? }.to(true) }
64
+ end
65
+
66
+ describe '#reload' do
67
+ specify { expect(association.reload).to eq([]) }
68
+
69
+ specify { expect(existing_association.reload).to eq(existing_book.authors) }
70
+
71
+ context do
72
+ before { existing_association.reader.last.name = 'Conan' }
73
+ specify { expect { existing_association.reload }
74
+ .to change { existing_association.reader.map(&:name) }
75
+ .from(['Conan']).to(['Rick']) }
76
+ end
77
+ end
78
+
79
+ describe '#reader' do
80
+ specify { expect(association.reader).to eq([]) }
81
+ specify { expect(association.reader).to be_a ActiveData::Model::Associations::Collection::Referenced }
82
+
83
+ specify { expect(existing_association.reader.first).to be_a Author }
84
+ specify { expect(existing_association.reader.first).to be_persisted }
85
+
86
+ context do
87
+ before { association.concat author }
88
+ specify { expect(association.reader.last).to be_a Author }
89
+ specify { expect(association.reader.size).to eq(1) }
90
+ specify { expect(association.reader(true)).to eq([author]) }
91
+ end
92
+
93
+ context do
94
+ before { existing_association.concat other }
95
+ specify { expect(existing_association.reader.size).to eq(2) }
96
+ specify { expect(existing_association.reader.last.name).to eq('Ben') }
97
+ specify { expect(existing_association.reader(true).size).to eq(2) }
98
+ specify { expect(existing_association.reader(true).last.name).to eq('Ben') }
99
+ end
100
+
101
+ context 'proxy missing method delection' do
102
+ specify { expect(existing_association.reader).to respond_to(:where) }
103
+ specify { expect(existing_association.reader).to respond_to(:name_starts_with_a) }
104
+ end
105
+ end
106
+
107
+ describe '#writer' do
108
+ let(:new_author1) { Author.create!(name: 'John') }
109
+ let(:new_author2) { Author.create!(name: 'Adam') }
110
+ let(:new_author3) { Author.new(name: 'Jane') }
111
+
112
+ specify { expect { association.writer([Dummy.new]) }
113
+ .to raise_error ActiveData::AssociationTypeMismatch }
114
+
115
+ specify { expect { association.writer(nil) }.to raise_error NoMethodError }
116
+ specify { expect { association.writer(new_author1) }.to raise_error NoMethodError }
117
+ specify { expect(association.writer([])).to eq([]) }
118
+
119
+ specify { expect(association.writer([new_author1])).to eq([new_author1]) }
120
+ specify { expect { association.writer([new_author1]) }
121
+ .to change { association.reader.map(&:name) }.from([]).to(['John']) }
122
+ specify { expect { association.writer([new_author1]) }
123
+ .to change { book.read_attribute(:author_ids) }
124
+ .from([]).to([new_author1.id]) }
125
+
126
+ specify { expect { existing_association.writer([new_author1, Dummy.new, new_author2]) }
127
+ .to raise_error ActiveData::AssociationTypeMismatch }
128
+ specify { expect { existing_association.writer([new_author1, Dummy.new, new_author2]) rescue nil }
129
+ .not_to change { existing_book.read_attribute(:author_ids) } }
130
+ specify { expect { existing_association.writer([new_author1, Dummy.new, new_author2]) rescue nil }
131
+ .not_to change { existing_association.reader } }
132
+
133
+ specify { expect { existing_association.writer(nil) }.to raise_error NoMethodError }
134
+ specify { expect { existing_association.writer(nil) rescue nil }
135
+ .not_to change { existing_book.read_attribute(:author_ids) } }
136
+ specify { expect { existing_association.writer(nil) rescue nil }
137
+ .not_to change { existing_association.reader } }
138
+
139
+ specify { expect(existing_association.writer([])).to eq([]) }
140
+ specify { expect { existing_association.writer([]) }
141
+ .to change { existing_book.read_attribute(:author_ids) }.to([]) }
142
+ specify { expect { existing_association.writer([]) }
143
+ .to change { existing_association.reader }.from([author]).to([]) }
144
+
145
+ specify { expect(existing_association.writer([new_author1, new_author2])).to eq([new_author1, new_author2]) }
146
+ specify { expect { existing_association.writer([new_author1, new_author2]) }
147
+ .to change { existing_association.reader.map(&:name) }
148
+ .from(['Rick']).to(['John', 'Adam']) }
149
+ specify { expect { existing_association.writer([new_author1, new_author2]) }
150
+ .to change { existing_book.read_attribute(:author_ids) }
151
+ .from([author.id]).to([new_author1.id, new_author2.id]) }
152
+
153
+ specify { expect { existing_association.writer([new_author3]) }
154
+ .to change { existing_association.target }.from([author]).to([new_author3]) }
155
+ specify { expect { existing_association.writer([new_author3]) }
156
+ .to change { existing_book.read_attribute(:author_ids) }
157
+ .from([author.id]).to([nil]) }
158
+ end
159
+
160
+ describe '#concat' do
161
+ let(:new_author1) { Author.create!(name: 'John') }
162
+ let(:new_author2) { Author.create!(name: 'Adam') }
163
+
164
+ specify { expect { association.concat(Dummy.new) }
165
+ .to raise_error ActiveData::AssociationTypeMismatch }
166
+
167
+ specify { expect { association.concat(nil) }.to raise_error ActiveData::AssociationTypeMismatch }
168
+ specify { expect(association.concat([])).to eq([]) }
169
+ specify { expect(existing_association.concat([])).to eq(existing_book.authors) }
170
+ specify { expect(existing_association.concat).to eq(existing_book.authors) }
171
+
172
+ specify { expect(association.concat(new_author1)).to eq([new_author1]) }
173
+ specify { expect { association.concat(new_author1) }
174
+ .to change { association.reader.map(&:name) }.from([]).to(['John']) }
175
+ specify { expect { association.concat(new_author1) }
176
+ .to change { book.read_attribute(:author_ids) }.from([]).to([1]) }
177
+
178
+ specify { expect { existing_association.concat(new_author1, Dummy.new, new_author2) }
179
+ .to raise_error ActiveData::AssociationTypeMismatch }
180
+ specify { expect { existing_association.concat(new_author1, Dummy.new, new_author2) rescue nil }
181
+ .to change { existing_book.read_attribute(:author_ids) }
182
+ .from([author.id]).to([author.id, new_author1.id]) }
183
+ specify { expect { existing_association.concat(new_author1, Dummy.new, new_author2) rescue nil }
184
+ .to change { existing_association.reader.map(&:name) }
185
+ .from(['Rick']).to(['Rick', 'John']) }
186
+
187
+ specify { expect(existing_association.concat(new_author1, new_author2))
188
+ .to eq([author, new_author1, new_author2]) }
189
+ specify { expect { existing_association.concat([new_author1, new_author2]) }
190
+ .to change { existing_association.reader.map(&:name) }
191
+ .from(['Rick']).to(['Rick', 'John', 'Adam']) }
192
+ specify { expect { existing_association.concat([new_author1, new_author2]) }
193
+ .to change { existing_book.read_attribute(:author_ids) }
194
+ .from([author.id]).to([author.id, new_author1.id, new_author2.id]) }
195
+ end
196
+ end