active_data 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Conventions do
5
+ let(:model) { stub_model }
6
+
7
+ specify { expect([model].flatten).to eq([model]) }
8
+ specify { expect(model.i18n_scope).to eq(:active_data) }
9
+ specify { expect(model.new).not_to be_persisted }
10
+ specify { expect(model.new).to be_new_record }
11
+ specify { expect(model.new).to be_new_object }
12
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Dirty do
5
+ before do
6
+ stub_class(:author, ActiveRecord::Base) { }
7
+ stub_model :premodel do
8
+ include ActiveData::Model::Persistence
9
+ include ActiveData::Model::Localization
10
+ include ActiveData::Model::Associations
11
+
12
+ attribute :age, Integer, default: 33
13
+ alias_attribute :a, :age
14
+ end
15
+ stub_model :model, Premodel do
16
+ include ActiveData::Model::Dirty
17
+
18
+ references_one :author
19
+ references_many :authors
20
+ embeds_one :something do
21
+ attribute :value, String
22
+ end
23
+ represents :name, of: :author
24
+ alias_attribute :n, :name
25
+ collection :numbers, Integer
26
+ localized :title, String
27
+ end
28
+ end
29
+
30
+ let(:author) { Author.create!(name: 'Name') }
31
+ let(:other_author) { Author.create!(name: 'Other') }
32
+
33
+ specify { expect(Model.new.changes).to eq({}) }
34
+ specify { expect(Model.new.tap { |m| m.create_something(value: 'Value') }.changes).to eq({}) }
35
+
36
+ specify { expect(Model.new(author: author).changes).to eq('author_id' => [nil, author.id]) }
37
+ specify { expect(Model.new(author_id: author.id).changes).to eq('author_id' => [nil, author.id]) }
38
+ specify { expect(Model.new(authors: [author]).changes).to eq('author_ids' => [[], [author.id]]) }
39
+ specify { expect(Model.new(author_ids: [author.id]).changes).to eq('author_ids' => [[], [author.id]]) }
40
+
41
+ specify { expect(Model.new(author: author, name: 'Name2').changes)
42
+ .to eq('author_id' => [nil, author.id], 'name' => ['Name', 'Name2']) }
43
+
44
+ specify { expect(Model.instantiate(author_id: other_author.id)
45
+ .tap { |m| m.update(author_id: author.id) }.changes)
46
+ .to eq('author_id' => [other_author.id, author.id]) }
47
+ specify { expect(Model.instantiate(author_id: other_author.id)
48
+ .tap { |m| m.update(author: author) }.changes)
49
+ .to eq('author_id' => [other_author.id, author.id]) }
50
+ specify { expect(Model.instantiate(author_ids: [other_author.id])
51
+ .tap { |m| m.update(author_ids: [author.id]) }.changes)
52
+ .to eq('author_ids' => [[other_author.id], [author.id]]) }
53
+ specify { expect(Model.instantiate(author_ids: [other_author.id])
54
+ .tap { |m| m.update(authors: [author]) }.changes)
55
+ .to eq('author_ids' => [[other_author.id], [author.id]]) }
56
+
57
+ specify { expect(Model.new(a: 'blabla').changes).to eq('age' => [33, nil]) }
58
+ specify { expect(Model.new(a: '42').changes).to eq('age' => [33, 42]) }
59
+ specify { expect(Model.instantiate(age: '42').changes).to eq({}) }
60
+ specify { expect(Model.instantiate(age: '42').tap { |m| m.update(a: '43') }.changes).to eq('age' => [42, 43]) }
61
+ specify { expect(Model.new(a: '42').tap { |m| m.update(a: '43') }.changes).to eq('age' => [33, 43]) }
62
+ specify { expect(Model.new(numbers: '42').changes).to eq('numbers' => [[], [42]]) }
63
+
64
+ # Have no idea how should it work right now
65
+ specify { expect(Model.new(title: 'Hello').changes).to eq('title' => [{}, 'Hello']) }
66
+ specify { expect(Model.new(title_translations: {en: 'Hello'}).changes).to eq('title' => [{}, 'Hello']) }
67
+
68
+ specify { expect(Model.new).not_to respond_to :something_changed? }
69
+ specify { expect(Model.new).to respond_to :n_changed? }
70
+
71
+ specify { expect(Model.new(a: '42')).to be_age_changed }
72
+ specify { expect(Model.new(a: '42')).to be_a_changed }
73
+ specify { expect(Model.new(a: '42').age_change).to eq([33, 42]) }
74
+ specify { expect(Model.new(a: '42').a_change).to eq([33, 42]) }
75
+ end
@@ -0,0 +1,330 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Lifecycle do
5
+ context do
6
+ before do
7
+ stub_model(:user) do
8
+ include ActiveData::Model::Lifecycle
9
+ end
10
+ end
11
+
12
+ subject { User.new }
13
+
14
+ [:save, :create, :update, :destroy].each do |action|
15
+ specify { expect { subject.public_send "_#{action}_performer=", '' }.to raise_error NoMethodError }
16
+ end
17
+
18
+ context 'performer execution' do
19
+ let(:foo) { true }
20
+
21
+ specify { expect { subject.save { foo } }.to raise_error NameError }
22
+ specify { expect { subject.save { |instance| attributes } }.to raise_error NameError }
23
+ specify { expect(subject.save { attributes }).to eq(true) }
24
+ specify { expect(subject.save { |instance| foo }).to eq(true) }
25
+ end
26
+
27
+ context 'save' do
28
+ specify { expect { subject.save }.to raise_error ActiveData::UnsavableObject }
29
+ specify { expect { subject.save! }.to raise_error ActiveData::UnsavableObject }
30
+
31
+ specify { expect(subject.save { true }).to eq(true) }
32
+ specify { expect(subject.save! { true }).to eq(true) }
33
+
34
+ context 'instance performer' do
35
+ before { subject.define_save { false } }
36
+
37
+ specify { expect(subject.save).to eq(false) }
38
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
39
+ end
40
+
41
+ context 'create performer' do
42
+ before { User.define_create { true } }
43
+
44
+ specify { expect(subject.save).to eq(true) }
45
+ specify { expect(subject.save!).to eq(true) }
46
+
47
+ context do
48
+ subject { User.create }
49
+
50
+ specify { expect { subject.save }.to raise_error ActiveData::UnsavableObject }
51
+ specify { expect { subject.save! }.to raise_error ActiveData::UnsavableObject }
52
+ end
53
+
54
+ context 'instance performer' do
55
+ before { subject.define_create { false } }
56
+
57
+ specify { expect(subject.save).to eq(false) }
58
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
59
+ end
60
+ end
61
+
62
+ context 'update performer' do
63
+ before { User.define_update { true } }
64
+
65
+ specify { expect { subject.save }.to raise_error ActiveData::UnsavableObject }
66
+ specify { expect { subject.save! }.to raise_error ActiveData::UnsavableObject }
67
+
68
+ context do
69
+ subject { User.new.tap { |u| u.save { true } } }
70
+
71
+ specify { expect(subject.save).to eq(true) }
72
+ specify { expect(subject.save!).to eq(true) }
73
+
74
+ context 'instance performer' do
75
+ before { subject.define_update { false } }
76
+
77
+ specify { expect(subject.save).to eq(false) }
78
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'performers execution' do
84
+ before do
85
+ stub_model(:user) do
86
+ include ActiveData::Model::Lifecycle
87
+
88
+ attribute :actions, Array, default: []
89
+
90
+ def append action
91
+ self.actions = actions + [action]
92
+ end
93
+
94
+ define_create { append :create }
95
+ define_update { append :update }
96
+ define_destroy { append :destroy }
97
+ end
98
+ end
99
+
100
+ subject { User.new }
101
+
102
+ specify do
103
+ subject.destroy
104
+ subject.save
105
+ subject.save
106
+ subject.destroy
107
+ subject.destroy
108
+ subject.save
109
+ expect(subject.actions).to eq([:destroy, :create, :update, :destroy, :destroy, :create])
110
+ end
111
+ end
112
+ end
113
+
114
+ context 'destroy' do
115
+ specify { expect { subject.destroy }.to raise_error ActiveData::UndestroyableObject }
116
+ specify { expect { subject.destroy! }.to raise_error ActiveData::UndestroyableObject }
117
+
118
+ specify { expect(subject.destroy { true }).to be_a User }
119
+ specify { expect(subject.destroy! { true }).to be_a User }
120
+
121
+ context 'instance performer' do
122
+ before { subject.define_save { false } }
123
+
124
+ specify { expect(subject.save).to eq(false) }
125
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
126
+ end
127
+ end
128
+ end
129
+
130
+ context do
131
+ before do
132
+ stub_class(:storage) do
133
+ def self.storage
134
+ @storage ||= {}
135
+ end
136
+ end
137
+ end
138
+
139
+ before do
140
+ stub_model(:user) do
141
+ include ActiveData::Model::Lifecycle
142
+ delegate :generate_id, to: 'self.class'
143
+
144
+ attribute :id, Integer, &:generate_id
145
+ attribute :name, String
146
+ validates :id, :name, presence: true
147
+
148
+ def self.generate_id
149
+ @id = @id.to_i.next
150
+ end
151
+
152
+ define_save do
153
+ Storage.storage.merge!(id => attributes.symbolize_keys)
154
+ end
155
+
156
+ define_destroy do
157
+ Storage.storage.delete(id)
158
+ end
159
+ end
160
+ end
161
+
162
+ describe '.create' do
163
+ subject { User.create(name: 'Jonny') }
164
+
165
+ it { is_expected.to be_a User }
166
+ it { is_expected.to be_valid }
167
+ it { is_expected.to be_persisted }
168
+
169
+ context 'invalid' do
170
+ subject { User.create }
171
+
172
+ it { is_expected.to be_a User }
173
+ it { is_expected.to be_invalid }
174
+ it { is_expected.not_to be_persisted }
175
+ end
176
+ end
177
+
178
+ describe '.create!' do
179
+ subject { User.create!(name: 'Jonny') }
180
+
181
+ it { is_expected.to be_a User }
182
+ it { is_expected.to be_valid }
183
+ it { is_expected.to be_persisted }
184
+
185
+ context 'invalid' do
186
+ subject { User.create! }
187
+
188
+ specify { expect { subject }.to raise_error ActiveData::ValidationError }
189
+ end
190
+ end
191
+
192
+ describe '#update, #update!' do
193
+ subject { User.new }
194
+
195
+ specify { expect { subject.update(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
196
+ specify { expect { subject.update!(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
197
+
198
+ specify { expect { subject.update({}) }.not_to change { subject.persisted? } }
199
+ specify { expect { subject.update!({}) }.to raise_error ActiveData::ValidationError }
200
+
201
+ specify { expect { subject.update(name: 'Jonny') }
202
+ .to change { Storage.storage.keys }.from([]).to([subject.id]) }
203
+ specify { expect { subject.update!(name: 'Jonny') }
204
+ .to change { Storage.storage.keys }.from([]).to([subject.id]) }
205
+ end
206
+
207
+ describe '#update_attributes, #update_attributes!' do
208
+ subject { User.new }
209
+
210
+ specify { expect { subject.update_attributes(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
211
+ specify { expect { subject.update_attributes!(name: 'Jonny') }.to change { subject.persisted? }.from(false).to(true) }
212
+
213
+ specify { expect { subject.update_attributes({}) }.not_to change { subject.persisted? } }
214
+ specify { expect { subject.update_attributes!({}) }.to raise_error ActiveData::ValidationError }
215
+
216
+ specify { expect { subject.update_attributes(name: 'Jonny') }
217
+ .to change { Storage.storage.keys }.from([]).to([subject.id]) }
218
+ specify { expect { subject.update_attributes!(name: 'Jonny') }
219
+ .to change { Storage.storage.keys }.from([]).to([subject.id]) }
220
+ end
221
+
222
+ describe '#save, #save!' do
223
+ context 'invalid' do
224
+ subject { User.new }
225
+
226
+ it { is_expected.to be_invalid }
227
+ it { is_expected.not_to be_persisted }
228
+
229
+ specify { expect(subject.save).to eq(false) }
230
+ specify { expect { subject.save! }.to raise_error ActiveData::ValidationError }
231
+
232
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
233
+ specify { expect { subject.save! rescue nil }.not_to change { subject.persisted? } }
234
+ end
235
+
236
+ context 'create' do
237
+ subject { User.new(name: 'Jonny') }
238
+
239
+ it { is_expected.to be_valid }
240
+ it { is_expected.not_to be_persisted }
241
+
242
+ specify { expect(subject.save).to eq(true) }
243
+ specify { expect(subject.save!).to eq(true) }
244
+
245
+ specify { expect { subject.save }.to change { subject.persisted? }.from(false).to(true) }
246
+ specify { expect { subject.save! }.to change { subject.persisted? }.from(false).to(true) }
247
+
248
+ specify { expect { subject.save }.to change { Storage.storage.keys }.from([]).to([subject.id]) }
249
+ specify { expect { subject.save! }.to change { Storage.storage.keys }.from([]).to([subject.id]) }
250
+
251
+ context 'save failed' do
252
+ before { User.define_save { false } }
253
+
254
+ it { is_expected.not_to be_persisted }
255
+
256
+ specify { expect(subject.save).to eq(false) }
257
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
258
+
259
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
260
+ specify { expect { subject.save! rescue nil }.not_to change { subject.persisted? } }
261
+ end
262
+ end
263
+
264
+ context 'update' do
265
+ subject! { User.new(name: 'Jonny').tap(&:save).tap { |u| u.name = 'Jimmy' } }
266
+
267
+ it { is_expected.to be_valid }
268
+ it { is_expected.to be_persisted }
269
+
270
+ specify { expect(subject.save).to eq(true) }
271
+ specify { expect(subject.save!).to eq(true) }
272
+
273
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
274
+ specify { expect { subject.save! }.not_to change { subject.persisted? } }
275
+
276
+ specify { expect { subject.save }.to change { Storage.storage[subject.id] }
277
+ .from(hash_including(name: 'Jonny')).to(hash_including(name: 'Jimmy')) }
278
+ specify { expect { subject.save! }.to change { Storage.storage[subject.id] }
279
+ .from(hash_including(name: 'Jonny')).to(hash_including(name: 'Jimmy')) }
280
+
281
+ context 'save failed' do
282
+ before { User.define_save { false } }
283
+
284
+ it { is_expected.to be_persisted }
285
+
286
+ specify { expect(subject.save).to eq(false) }
287
+ specify { expect { subject.save! }.to raise_error ActiveData::ObjectNotSaved }
288
+
289
+ specify { expect { subject.save }.not_to change { subject.persisted? } }
290
+ specify { expect { subject.save! rescue nil }.not_to change { subject.persisted? } }
291
+ end
292
+ end
293
+ end
294
+
295
+ describe '#destroy, #destroy!' do
296
+ subject { User.create(name: 'Jonny') }
297
+
298
+ it { is_expected.to be_valid }
299
+ it { is_expected.to be_persisted }
300
+ it { is_expected.not_to be_destroyed }
301
+
302
+ specify { expect(subject.destroy).to eq(subject) }
303
+ specify { expect(subject.destroy!).to eq(subject) }
304
+
305
+ specify { expect { subject.destroy }.to change { subject.persisted? }.from(true).to(false) }
306
+ specify { expect { subject.destroy! }.to change { subject.persisted? }.from(true).to(false) }
307
+
308
+ specify { expect { subject.destroy }.to change { subject.destroyed? }.from(false).to(true) }
309
+ specify { expect { subject.destroy! }.to change { subject.destroyed? }.from(false).to(true) }
310
+
311
+ specify { expect { subject.destroy }.to change { Storage.storage.keys }.from([subject.id]).to([]) }
312
+ specify { expect { subject.destroy! }.to change { Storage.storage.keys }.from([subject.id]).to([]) }
313
+
314
+ context 'save failed' do
315
+ before { User.define_destroy { false } }
316
+
317
+ it { is_expected.to be_persisted }
318
+
319
+ specify { expect(subject.destroy).to eq(subject) }
320
+ specify { expect { subject.destroy! }.to raise_error ActiveData::ObjectNotDestroyed }
321
+
322
+ specify { expect { subject.destroy }.not_to change { subject.persisted? } }
323
+ specify { expect { subject.destroy! rescue nil }.not_to change { subject.persisted? } }
324
+
325
+ specify { expect { subject.destroy }.not_to change { subject.destroyed? } }
326
+ specify { expect { subject.destroy! rescue nil }.not_to change { subject.destroyed? } }
327
+ end
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'nested attributes' do
4
+ before do
5
+ stub_model :project do
6
+ include ActiveData::Model::Primary
7
+ include ActiveData::Model::Lifecycle
8
+
9
+ primary :slug, String
10
+ attribute :title, String
11
+ end
12
+
13
+ stub_model :profile do
14
+ include ActiveData::Model::Primary
15
+ include ActiveData::Model::Lifecycle
16
+
17
+ primary :identifier
18
+ attribute :first_name, String
19
+ end
20
+ end
21
+
22
+ context 'embeds_one' do
23
+ let(:user) { User.new }
24
+
25
+ specify { expect { user.profile_attributes = {} }.to change { user.profile }.to(an_instance_of(Profile)) }
26
+ specify { expect { user.profile_attributes = {first_name: 'User'} }.to change { user.profile.try(:first_name) }.to('User') }
27
+ specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User'} }.to raise_error ActiveData::ObjectNotFound }
28
+
29
+ context ':reject_if' do
30
+ context do
31
+ before { User.accepts_nested_attributes_for :profile, reject_if: :all_blank }
32
+ specify { expect { user.profile_attributes = {first_name: ''} }.not_to change { user.profile } }
33
+ end
34
+
35
+ context do
36
+ before { User.accepts_nested_attributes_for :profile, reject_if: ->(attributes) { attributes['first_name'].blank? } }
37
+ specify { expect { user.profile_attributes = {first_name: ''} }.not_to change { user.profile } }
38
+ end
39
+ end
40
+
41
+ context 'existing' do
42
+ let(:profile) { Profile.new(first_name: 'User') }
43
+ let(:user) { User.new profile: profile }
44
+
45
+ specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User'} }.to raise_error ActiveData::ObjectNotFound }
46
+ specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1'} }.to change { user.profile.first_name }.to('User 1') }
47
+ specify { expect { user.profile_attributes = {first_name: 'User 1'} }.to change { user.profile.first_name }.to('User 1') }
48
+ specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'} }.not_to change { user.profile.first_name } }
49
+ specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'}; user.save { true } }.not_to change { user.profile.first_name } }
50
+ specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} }.to change { user.profile.first_name }.to('User 1') }
51
+ specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'}; user.save { true } }.to change { user.profile.first_name }.to('User 1') }
52
+
53
+ context ':allow_destroy' do
54
+ before { User.accepts_nested_attributes_for :profile, allow_destroy: true }
55
+
56
+ specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'} }.not_to change { user.profile.first_name } }
57
+ specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'}; user.save { true } }.not_to change { user.profile.first_name } }
58
+ specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} }.to change { user.profile.first_name }.to('User 1') }
59
+ specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'}; user.save { true } }.to change { user.profile }.to(nil) }
60
+ end
61
+
62
+ context ':update_only' do
63
+ before { User.accepts_nested_attributes_for :profile, update_only: true }
64
+
65
+ specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User 1'} }
66
+ .to change { user.profile.first_name }.to('User 1') }
67
+ end
68
+ end
69
+
70
+ context 'not primary' do
71
+ before do
72
+ stub_model :profile do
73
+ include ActiveData::Model::Lifecycle
74
+
75
+ attribute :identifier, Integer
76
+ attribute :first_name, String
77
+ end
78
+ end
79
+
80
+ specify { expect { user.profile_attributes = {} }.to change { user.profile }.to(an_instance_of(Profile)) }
81
+ specify { expect { user.profile_attributes = {first_name: 'User'} }.to change { user.profile.try(:first_name) }.to('User') }
82
+
83
+ context do
84
+ let(:profile) { Profile.new(first_name: 'User') }
85
+ let(:user) { User.new profile: profile }
86
+
87
+ specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User 1'} }
88
+ .to change { user.profile.first_name }.to('User 1') }
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'embeds_many' do
94
+ let(:user) { User.new }
95
+
96
+ specify { expect { user.projects_attributes = {} }.not_to change { user.projects } }
97
+ specify { expect { user.projects_attributes = [{title: 'Project 1'}, {title: 'Project 2'}] }
98
+ .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) }
99
+ specify { expect { user.projects_attributes = {1 => {title: 'Project 1'}, 2 => {title: 'Project 2'}} }
100
+ .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) }
101
+ specify { expect { user.projects_attributes = [{slug: 42, title: 'Project 1'}, {title: 'Project 2'}] }
102
+ .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) }
103
+ specify { expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] }
104
+ .to change { user.projects.map(&:title) }.to(['', 'Project 2']) }
105
+
106
+ context ':limit' do
107
+ before { User.accepts_nested_attributes_for :projects, limit: 1 }
108
+
109
+ specify { expect { user.projects_attributes = [{title: 'Project 1'}] }
110
+ .to change { user.projects.map(&:title) }.to(['Project 1']) }
111
+ specify { expect { user.projects_attributes = [{title: 'Project 1'}, {title: 'Project 2'}] }
112
+ .to raise_error ActiveData::TooManyObjects }
113
+ end
114
+
115
+ context ':reject_if' do
116
+ context do
117
+ before { User.accepts_nested_attributes_for :projects, reject_if: :all_blank }
118
+ specify { expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] }
119
+ .to change { user.projects.map(&:title) }.to(['Project 2']) }
120
+ end
121
+
122
+ context do
123
+ before { User.accepts_nested_attributes_for :projects, reject_if: ->(attributes) { attributes['title'].blank? } }
124
+ specify { expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] }
125
+ .to change { user.projects.map(&:title) }.to(['Project 2']) }
126
+ end
127
+
128
+ context do
129
+ before { User.accepts_nested_attributes_for :projects, reject_if: ->(attributes) { attributes['foobar'].blank? } }
130
+ specify { expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] }
131
+ .not_to change { user.projects } }
132
+ end
133
+ end
134
+
135
+ context 'existing' do
136
+ let(:projects) { 2.times.map { |i| Project.new(title: "Project #{i.next}").tap { |pr| pr.slug = 42+i } } }
137
+ let(:user) { User.new projects: projects }
138
+
139
+ specify { expect { user.projects_attributes = [
140
+ {slug: projects.first.slug.to_i, title: 'Project 3'},
141
+ {title: 'Project 4'}
142
+ ] }
143
+ .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2', 'Project 4']) }
144
+ specify { expect { user.projects_attributes = [
145
+ {slug: projects.first.slug.to_i, title: 'Project 3'},
146
+ {slug: 33, title: 'Project 4'}
147
+ ] }
148
+ .to change { user.projects.map(&:slug) }.to(['42', '43', '33']) }
149
+ specify { expect { user.projects_attributes = [
150
+ {slug: projects.first.slug.to_i, title: 'Project 3'},
151
+ {slug: 33, title: 'Project 4', _destroy: 1}
152
+ ] }
153
+ .not_to change { user.projects.map(&:slug) } }
154
+ specify { expect { user.projects_attributes = {
155
+ 1 => {slug: projects.first.slug.to_i, title: 'Project 3'},
156
+ 2 => {title: 'Project 4'}
157
+ } }
158
+ .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2', 'Project 4']) }
159
+ specify { expect { user.projects_attributes = [
160
+ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'},
161
+ {title: 'Project 4', _destroy: '1'}
162
+ ] }
163
+ .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) }
164
+ specify { expect { user.projects_attributes = [
165
+ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'},
166
+ {title: 'Project 4', _destroy: '1'}
167
+ ]
168
+ user.save { true } }
169
+ .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) }
170
+
171
+ context ':allow_destroy' do
172
+ before { User.accepts_nested_attributes_for :projects, allow_destroy: true }
173
+
174
+ specify { expect { user.projects_attributes = [
175
+ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'},
176
+ {title: 'Project 4', _destroy: '1'}
177
+ ] }
178
+ .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) }
179
+ specify { expect { user.projects_attributes = [
180
+ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'},
181
+ {title: 'Project 4', _destroy: '1'}
182
+ ]
183
+ user.save { true } }
184
+ .to change { user.projects.map(&:title) }.to(['Project 2']) }
185
+ end
186
+ end
187
+
188
+ context 'primary absence causes exception' do
189
+ before do
190
+ stub_model :project do
191
+ include ActiveData::Model::Primary
192
+ include ActiveData::Model::Lifecycle
193
+
194
+ attribute :slug, String
195
+ attribute :title, String
196
+ end
197
+ end
198
+
199
+ specify { expect { user.projects_attributes = {} }.to raise_error ActiveData::UndefinedPrimaryAttribute }
200
+ end
201
+ end
202
+ end