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
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'lib/active_data/model/nested_attributes'
4
+
5
+ describe ActiveData::ActiveRecord::NestedAttributes do
6
+ before do
7
+ stub_class(:user, ActiveRecord::Base) do
8
+ embeds_one :profile
9
+ embeds_many :projects
10
+
11
+ accepts_nested_attributes_for :profile, :projects
12
+ end
13
+ end
14
+
15
+ include_examples 'nested attributes'
16
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Config do
5
+ subject { ActiveData::Config.send :new }
6
+
7
+ describe '#include_root_in_json' do
8
+ its(:include_root_in_json) { should == false }
9
+ specify { expect { subject.include_root_in_json = true }
10
+ .to change { subject.include_root_in_json }.from(false).to(true) }
11
+ end
12
+
13
+ describe '#i18n_scope' do
14
+ its(:i18n_scope) { should == :active_data }
15
+ specify { expect { subject.i18n_scope = :data_model }
16
+ .to change { subject.i18n_scope }.from(:active_data).to(:data_model) }
17
+ end
18
+
19
+ describe '#logger' do
20
+ its(:logger) { should be_a Logger }
21
+ end
22
+
23
+ describe '#primary_attribute' do
24
+ its(:primary_attribute) { should == :id }
25
+ specify { expect { subject.primary_attribute = :identified }
26
+ .to change { subject.primary_attribute }.from(:id).to(:identified) }
27
+ end
28
+
29
+ describe '#normalizer' do
30
+ specify { expect { subject.normalizer(:name) { } }
31
+ .to change { subject.normalizer(:name) rescue nil }.from(nil).to(an_instance_of(Proc)) }
32
+ specify { expect { subject.normalizer(:wrong) }.to raise_error ActiveData::NormalizerMissing }
33
+ end
34
+
35
+ describe '#typecaster' do
36
+ specify { expect { subject.typecaster('Object') { } }
37
+ .to change { subject.typecaster(Time, Object) rescue nil }.from(nil).to(an_instance_of(Proc)) }
38
+ specify { expect { subject.typecaster('Object') { } }
39
+ .to change { subject.typecaster('time', 'object') rescue nil }.from(nil).to(an_instance_of(Proc)) }
40
+ specify { expect { subject.typecaster('Object') { } }
41
+ .to change { subject.typecaster(Object) rescue nil }.from(nil).to(an_instance_of(Proc)) }
42
+ specify { expect { subject.typecaster(Object) }.to raise_error ActiveData::TypecasterMissing }
43
+ end
44
+ end
@@ -2,92 +2,402 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe ActiveData::Model::Associations::EmbedsMany do
5
+ before do
6
+ stub_model(:dummy) do
7
+ include ActiveData::Model::Associations
8
+ end
9
+
10
+ stub_model(:project) do
11
+ include ActiveData::Model::Lifecycle
5
12
 
6
- class ManyAssoc
7
- include ActiveData::Model
13
+ attribute :title, String
14
+ validates :title, presence: true
15
+ end
16
+ stub_model(:user) do
17
+ include ActiveData::Model::Persistence
18
+ include ActiveData::Model::Associations
8
19
 
9
- attribute :name
20
+ attribute :name, String
21
+ embeds_many :projects
22
+ end
10
23
  end
11
24
 
12
- let(:noassoc) do
13
- Class.new do
14
- include ActiveData::Model
25
+ let(:user) { User.new }
26
+ let(:association) { user.association(:projects) }
15
27
 
16
- attribute :name
17
- end
28
+ let(:existing_user) { User.instantiate name: 'Rick', projects: [{title: 'Genesis'}] }
29
+ let(:existing_association) { existing_user.association(:projects) }
30
+
31
+ describe 'user#association' do
32
+ specify { expect(association).to be_a described_class }
33
+ specify { expect(association).to eq(user.association(:projects)) }
18
34
  end
19
35
 
20
- let(:klass) do
21
- Class.new(noassoc) do
22
- embeds_many :many_assocs
36
+ context 'performers' do
37
+ let(:user) { User.new(projects: [Project.new(title: 'Project 1')]) }
38
+
39
+ specify do
40
+ p2 = user.projects.build(title: 'Project 2')
41
+ p3 = user.projects.build(title: 'Project 3')
42
+ p4 = user.projects.create(title: 'Project 4')
43
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 4'}])
44
+ p2.save!
45
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 2'}, {'title' => 'Project 4'}])
46
+ p2.destroy!.destroy!
47
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 4'}])
48
+ user.projects.create(title: 'Project 5')
49
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 4'}, {'title' => 'Project 5'}])
50
+ p3.destroy!
51
+ user.projects.first.destroy!
52
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 4'}, {'title' => 'Project 5'}])
53
+ p4.destroy!.save!
54
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 4'}, {'title' => 'Project 5'}])
55
+ expect(user.projects.count).to eq(5)
56
+ user.projects.map(&:save!)
57
+ expect(user.read_attribute(:projects)).to eq([
58
+ {'title' => 'Project 1'}, {'title' => 'Project 2'}, {'title' => 'Project 3'},
59
+ {'title' => 'Project 4'}, {'title' => 'Project 5'}])
60
+ user.projects.map(&:destroy!)
61
+ expect(user.read_attribute(:projects)).to eq([])
62
+ user.projects.first(2).map(&:save!)
63
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 1'}, {'title' => 'Project 2'}])
64
+ expect(user.projects.reload.count).to eq(2)
65
+ p3 = user.projects.create!(title: 'Project 3')
66
+ expect(user.read_attribute(:projects)).to eq([
67
+ {'title' => 'Project 1'}, {'title' => 'Project 2'}, {'title' => 'Project 3'}])
68
+ p3.destroy!
69
+ expect(user.read_attribute(:projects)).to eq([{'title' => 'Project 1'}, {'title' => 'Project 2'}])
70
+ p4 = user.projects.create(title: 'Project 4')
71
+ expect(user.read_attribute(:projects)).to eq([
72
+ {'title' => 'Project 1'}, {'title' => 'Project 2'}, {'title' => 'Project 4'}])
23
73
  end
24
74
  end
25
75
 
26
- let(:inherited1) do
27
- Class.new(klass) do
28
- embeds_many :many_assocs_inherited1, class_name: ManyAssoc
29
- end
76
+ describe '#build' do
77
+ specify { expect(association.build).to be_a Project }
78
+ specify { expect(association.build).not_to be_persisted }
79
+
80
+ specify { expect { association.build(title: 'Swordfish') }
81
+ .not_to change { user.read_attribute(:projects) } }
82
+ specify { expect { association.build(title: 'Swordfish') }
83
+ .to change { association.reader.map(&:attributes) }
84
+ .from([]).to([{'title' => 'Swordfish'}]) }
85
+
86
+ specify { expect { existing_association.build(title: 'Swordfish') }
87
+ .not_to change { existing_user.read_attribute(:projects) } }
88
+ specify { expect { existing_association.build(title: 'Swordfish') }
89
+ .to change { existing_association.reader.map(&:attributes) }
90
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Swordfish'}]) }
30
91
  end
31
92
 
32
- let(:inherited2) do
33
- Class.new(noassoc) do
34
- embeds_many :many_assocs_inherited2, class_name: ManyAssoc
35
- end
93
+ describe '#create' do
94
+ specify { expect(association.create).to be_a Project }
95
+ specify { expect(association.create).not_to be_persisted }
96
+
97
+ specify { expect(association.create(title: 'Swordfish')).to be_a Project }
98
+ specify { expect(association.create(title: 'Swordfish')).to be_persisted }
99
+
100
+ specify { expect { association.create }
101
+ .not_to change { user.read_attribute(:projects) } }
102
+ specify { expect { association.create(title: 'Swordfish') }
103
+ .to change { user.read_attribute(:projects) }.from(nil).to([{'title' => 'Swordfish'}]) }
104
+ specify { expect { association.create(title: 'Swordfish') }
105
+ .to change { association.reader.map(&:attributes) }
106
+ .from([]).to([{'title' => 'Swordfish'}]) }
107
+
108
+ specify { expect { existing_association.create }
109
+ .not_to change { existing_user.read_attribute(:projects) } }
110
+ specify { expect { existing_association.create(title: 'Swordfish') }
111
+ .to change { existing_user.read_attribute(:projects) }
112
+ .from([{title: 'Genesis'}]).to([{title: 'Genesis'}, {'title' => 'Swordfish'}]) }
113
+ specify { expect { existing_association.create(title: 'Swordfish') }
114
+ .to change { existing_association.reader.map(&:attributes) }
115
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Swordfish'}]) }
36
116
  end
37
117
 
38
- subject { klass.new(name: 'world') }
118
+ describe '#create!' do
119
+ specify { expect { association.create! }.to raise_error ActiveData::ValidationError }
39
120
 
40
- context do
41
- specify { subject.many_assocs.should be_empty }
42
- specify { subject.many_assocs.should be_instance_of ManyAssoc.collection_class }
43
- specify { subject.many_assocs.count.should == 0 }
121
+ specify { expect(association.create!(title: 'Swordfish')).to be_a Project }
122
+ specify { expect(association.create!(title: 'Swordfish')).to be_persisted }
123
+
124
+ specify { expect { association.create! rescue nil }
125
+ .not_to change { user.read_attribute(:projects) } }
126
+ specify { expect { association.create! rescue nil }
127
+ .to change { association.reader.map(&:attributes) }.from([]).to([{'title' => nil}]) }
128
+ specify { expect { association.create!(title: 'Swordfish') }
129
+ .to change { user.read_attribute(:projects) }.from(nil).to([{'title' => 'Swordfish'}]) }
130
+ specify { expect { association.create!(title: 'Swordfish') }
131
+ .to change { association.reader.map(&:attributes) }
132
+ .from([]).to([{'title' => 'Swordfish'}]) }
133
+
134
+ specify { expect { existing_association.create! rescue nil }
135
+ .not_to change { existing_user.read_attribute(:projects) } }
136
+ specify { expect { existing_association.create! rescue nil }
137
+ .to change { existing_association.reader.map(&:attributes) }
138
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => nil}]) }
139
+ specify { expect { existing_association.create!(title: 'Swordfish') }
140
+ .to change { existing_user.read_attribute(:projects) }
141
+ .from([{title: 'Genesis'}]).to([{title: 'Genesis'}, {'title' => 'Swordfish'}]) }
142
+ specify { expect { existing_association.create!(title: 'Swordfish') }
143
+ .to change { existing_association.reader.map(&:attributes) }
144
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Swordfish'}]) }
44
145
  end
45
146
 
46
- context 'accessor with objects' do
47
- before { subject.many_assocs = [ManyAssoc.new(name: 'foo'), ManyAssoc.new(name: 'bar')] }
48
- specify { subject.many_assocs.count.should == 2 }
49
- specify { subject.many_assocs[0].name.should == 'foo' }
50
- specify { subject.many_assocs[1].name.should == 'bar' }
147
+ describe '#apply_changes' do
148
+ specify { expect { association.build; association.apply_changes }.to change { association.target.map(&:persisted?) }.to([false]) }
149
+ specify { expect { association.build(title: 'Genesis'); association.apply_changes }.to change { association.target.map(&:persisted?) }.to([true]) }
150
+ specify { expect {
151
+ existing_association.target.first.mark_for_destruction
152
+ existing_association.build(title: 'Swordfish');
153
+ existing_association.apply_changes
154
+ }.to change { existing_association.target.map(&:title) }.to(['Swordfish']) }
155
+ specify { expect {
156
+ existing_association.target.first.mark_for_destruction
157
+ existing_association.build(title: 'Swordfish');
158
+ existing_association.apply_changes
159
+ }.not_to change { existing_association.target.map(&:persisted?) } }
160
+ specify { expect {
161
+ existing_association.target.first.mark_for_destruction
162
+ existing_association.build(title: 'Swordfish');
163
+ existing_association.apply_changes
164
+ }.to change { existing_association.destroyed.map(&:title) }.from([]).to(['Genesis']) }
165
+ specify { expect {
166
+ existing_association.target.first.destroy!
167
+ existing_association.build(title: 'Swordfish');
168
+ existing_association.apply_changes
169
+ }.to change { existing_association.target.map(&:title) }.to(['Swordfish']) }
170
+ specify { expect {
171
+ existing_association.target.first.destroy!
172
+ existing_association.build(title: 'Swordfish');
173
+ existing_association.apply_changes
174
+ }.to change { existing_association.destroyed.map(&:title) }.from([]).to(['Genesis']) }
51
175
  end
52
176
 
53
- context 'accessor with attributes' do
54
- before { subject.many_assocs = [{ name: 'foo' }, { name: 'bar' }] }
55
- specify { subject.many_assocs.count.should == 2 }
56
- specify { subject.many_assocs[0].name.should == 'foo' }
57
- specify { subject.many_assocs[1].name.should == 'bar' }
177
+ describe '#apply_changes!' do
178
+ specify { expect { association.build; association.apply_changes! }.to raise_error ActiveData::AssociationChangesNotApplied }
179
+ specify { expect { association.build(title: 'Genesis'); association.apply_changes! }.to change { association.target.map(&:persisted?) }.to([true]) }
180
+ specify { expect {
181
+ existing_association.target.first.mark_for_destruction
182
+ existing_association.build(title: 'Swordfish');
183
+ existing_association.apply_changes!
184
+ }.to change { existing_association.target.map(&:title) }.to(['Swordfish']) }
185
+ specify { expect {
186
+ existing_association.target.first.mark_for_destruction
187
+ existing_association.build(title: 'Swordfish');
188
+ existing_association.apply_changes!
189
+ }.not_to change { existing_association.target.map(&:persisted?) } }
190
+ specify { expect {
191
+ existing_association.target.first.destroy!
192
+ existing_association.build(title: 'Swordfish');
193
+ existing_association.apply_changes!
194
+ }.to change { existing_association.target.map(&:title) }.to(['Swordfish']) }
58
195
  end
59
196
 
60
- context 'inheritance' do
61
- specify { noassoc.association_names.should == [] }
62
- specify { klass.association_names.should == %w(many_assocs) }
63
- specify { inherited1.association_names.should == %w(many_assocs many_assocs_inherited1) }
64
- specify { inherited2.association_names.should == %w(many_assocs_inherited2) }
197
+ describe '#target' do
198
+ specify { expect(association.target).to eq([]) }
199
+ specify { expect(existing_association.target).to eq(existing_user.projects) }
200
+ specify { expect { association.build }.to change { association.target.count }.to(1) }
65
201
  end
66
202
 
67
- describe '#instantiate' do
68
- subject { klass.instantiate name: 'Root', many_assocs: [{ name: 'foo' }, { name: 'bar' }] }
203
+ describe '#default' do
204
+ before { User.embeds_many :projects, default: -> { { title: 'Default' } } }
205
+ before do
206
+ Project.class_eval do
207
+ include ActiveData::Model::Primary
208
+ primary :title
209
+ end
210
+ end
211
+ let(:new_project) { Project.new.tap { |p| p.title = 'Project' } }
212
+ let(:existing_user) { User.instantiate name: 'Rick' }
213
+
214
+ specify { expect(association.target.map(&:title)).to eq(['Default']) }
215
+ specify { expect(association.target.map(&:new_record?)).to eq([true]) }
216
+ specify { expect { association.replace([new_project]) }.to change { association.target.map(&:title) }.to eq(['Project']) }
217
+ specify { expect { association.replace([]) }.to change { association.target }.to([]) }
218
+
219
+ specify { expect(existing_association.target).to eq([]) }
220
+ specify { expect { existing_association.replace([new_project]) }.to change { existing_association.target.map(&:title) }.to(['Project']) }
221
+ specify { expect { existing_association.replace([]) }.not_to change { existing_association.target } }
222
+
223
+ context do
224
+ before { Project.send(:include, ActiveData::Model::Dirty) }
225
+ specify { expect(association.target.any?(&:changed?)).to eq(false) }
226
+ end
227
+ end
69
228
 
70
- its('many_assocs.count') { should == 2 }
229
+ describe '#loaded?' do
230
+ specify { expect(association.loaded?).to eq(false) }
231
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
232
+ specify { expect { association.build }.to change { association.loaded? }.to(true) }
233
+ specify { expect { association.replace([]) }.to change { association.loaded? }.to(true) }
234
+ specify { expect { existing_association.replace([]) }.to change { existing_association.loaded? }.to(true) }
71
235
  end
72
236
 
73
- describe '#==' do
74
- let(:instance) { klass.new(name: 'world') }
75
- before { subject.many_assocs = [ManyAssoc.new(name: 'foo'), ManyAssoc.new(name: 'bar')] }
76
- specify { subject.should_not == instance }
237
+ describe '#reload' do
238
+ specify { expect(association.reload).to eq([]) }
239
+
240
+ specify { expect(existing_association.reload).to eq(existing_user.projects) }
77
241
 
78
242
  context do
79
- before { instance.many_assocs = [ManyAssoc.new(name: 'foo')] }
80
- specify { subject.should_not == instance }
243
+ before { association.build(title: 'Swordfish') }
244
+ specify { expect { association.reload }
245
+ .to change { association.reader.map(&:attributes) }.from([{'title' => 'Swordfish'}]).to([]) }
81
246
  end
82
247
 
83
248
  context do
84
- before { instance.many_assocs = [ManyAssoc.new(name: 'foo1'), ManyAssoc.new(name: 'bar')] }
85
- specify { subject.should_not == instance }
249
+ before { existing_association.build(title: 'Swordfish') }
250
+ specify { expect { existing_association.reload }
251
+ .to change { existing_association.reader.map(&:attributes) }
252
+ .from([{'title' => 'Genesis'}, {'title' => 'Swordfish'}]).to([{'title' => 'Genesis'}]) }
86
253
  end
254
+ end
255
+
256
+ describe '#clear' do
257
+ specify { expect(association.clear).to eq(true) }
258
+ specify { expect { association.clear }.not_to change { association.reader } }
259
+
260
+ specify { expect(existing_association.clear).to eq(true) }
261
+ specify { expect { existing_association.clear }
262
+ .to change { existing_association.reader.map(&:attributes) }.from([{'title' => 'Genesis'}]).to([]) }
263
+ specify { expect { existing_association.clear }
264
+ .to change { existing_user.read_attribute(:projects) }.from([{title: 'Genesis'}]).to([]) }
87
265
 
88
266
  context do
89
- before { instance.many_assocs = [ManyAssoc.new(name: 'foo'), ManyAssoc.new(name: 'bar')] }
90
- specify { subject.should == instance }
267
+ let(:existing_user) { User.instantiate name: 'Rick', projects: [{title: 'Genesis'}, {title: 'Swordfish'}] }
268
+ before { Project.send(:include, ActiveData::Model::Callbacks) }
269
+ if ActiveModel.version >= Gem::Version.new('5.0.0')
270
+ before { Project.before_destroy { throw :abort } }
271
+ else
272
+ before { Project.before_destroy { false } }
273
+ end
274
+
275
+ specify { expect(existing_association.clear).to eq(false) }
276
+ specify { expect { existing_association.clear }
277
+ .not_to change { existing_association.reader } }
278
+ specify { expect { existing_association.clear }
279
+ .not_to change { existing_user.read_attribute(:projects) } }
91
280
  end
92
281
  end
282
+
283
+ describe '#reader' do
284
+ specify { expect(association.reader).to eq([]) }
285
+
286
+ specify { expect(existing_association.reader.first).to be_a Project }
287
+ specify { expect(existing_association.reader.first).to be_persisted }
288
+
289
+ context do
290
+ before { association.build }
291
+ specify { expect(association.reader.last).to be_a Project }
292
+ specify { expect(association.reader.last).not_to be_persisted }
293
+ specify { expect(association.reader.size).to eq(1) }
294
+ specify { expect(association.reader(true)).to eq([]) }
295
+ end
296
+
297
+ context do
298
+ before { existing_association.build(title: 'Swordfish') }
299
+ specify { expect(existing_association.reader.size).to eq(2) }
300
+ specify { expect(existing_association.reader.last.title).to eq('Swordfish') }
301
+ specify { expect(existing_association.reader(true).size).to eq(1) }
302
+ specify { expect(existing_association.reader(true).last.title).to eq('Genesis') }
303
+ end
304
+ end
305
+
306
+ describe '#writer' do
307
+ let(:new_project1) { Project.new(title: 'Project 1') }
308
+ let(:new_project2) { Project.new(title: 'Project 2') }
309
+ let(:invalid_project) { Project.new }
310
+
311
+ specify { expect { association.writer([Dummy.new]) }
312
+ .to raise_error ActiveData::AssociationTypeMismatch }
313
+
314
+ specify { expect { association.writer(nil) }.to raise_error NoMethodError }
315
+ specify { expect { association.writer(new_project1) }.to raise_error NoMethodError }
316
+ specify { expect(association.writer([])).to eq([]) }
317
+
318
+ specify { expect(association.writer([new_project1])).to eq([new_project1]) }
319
+ specify { expect { association.writer([new_project1]) }
320
+ .to change { association.reader.map(&:attributes) }.from([]).to([{'title' => 'Project 1'}]) }
321
+ specify { expect { association.writer([new_project1]) }
322
+ .not_to change { user.read_attribute(:projects) } }
323
+
324
+ specify { expect { existing_association.writer([new_project1, invalid_project]) }
325
+ .to raise_error ActiveData::AssociationChangesNotApplied }
326
+ specify { expect { existing_association.writer([new_project1, invalid_project]) rescue nil }
327
+ .not_to change { existing_user.read_attribute(:projects) } }
328
+ specify { expect { existing_association.writer([new_project1, invalid_project]) rescue nil }
329
+ .not_to change { existing_association.reader } }
330
+
331
+ specify { expect { existing_association.writer([new_project1, Dummy.new, new_project2]) }
332
+ .to raise_error ActiveData::AssociationTypeMismatch }
333
+ specify { expect { existing_association.writer([new_project1, Dummy.new, new_project2]) rescue nil }
334
+ .not_to change { existing_user.read_attribute(:projects) } }
335
+ specify { expect { existing_association.writer([new_project1, Dummy.new, new_project2]) rescue nil }
336
+ .not_to change { existing_association.reader } }
337
+
338
+ specify { expect { existing_association.writer(nil) }.to raise_error NoMethodError }
339
+ specify { expect { existing_association.writer(nil) rescue nil }
340
+ .not_to change { existing_user.read_attribute(:projects) } }
341
+ specify { expect { existing_association.writer(nil) rescue nil }
342
+ .not_to change { existing_association.reader } }
343
+
344
+ specify { expect(existing_association.writer([])).to eq([]) }
345
+ specify { expect { existing_association.writer([]) }
346
+ .to change { existing_user.read_attribute(:projects) }.to([]) }
347
+ specify { expect { existing_association.writer([]) }
348
+ .to change { existing_association.reader }.to([]) }
349
+
350
+ specify { expect(existing_association.writer([new_project1, new_project2])).to eq([new_project1, new_project2]) }
351
+ specify { expect { existing_association.writer([new_project1, new_project2]) }
352
+ .to change { existing_association.reader.map(&:attributes) }
353
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Project 1'}, {'title' => 'Project 2'}]) }
354
+ specify { expect { existing_association.writer([new_project1, new_project2]) }
355
+ .to change { existing_user.read_attribute(:projects) }
356
+ .from([{title: 'Genesis'}]).to([{'title' => 'Project 1'}, {'title' => 'Project 2'}]) }
357
+ end
358
+
359
+ describe '#concat' do
360
+ let(:new_project1) { Project.new(title: 'Project 1') }
361
+ let(:new_project2) { Project.new(title: 'Project 2') }
362
+ let(:invalid_project) { Project.new }
363
+
364
+ specify { expect { association.concat(Dummy.new) }
365
+ .to raise_error ActiveData::AssociationTypeMismatch }
366
+
367
+ specify { expect { association.concat(nil) }.to raise_error ActiveData::AssociationTypeMismatch }
368
+ specify { expect(association.concat([])).to eq([]) }
369
+ specify { expect(existing_association.concat([])).to eq(existing_user.projects) }
370
+ specify { expect(existing_association.concat).to eq(existing_user.projects) }
371
+
372
+ specify { expect(association.concat(new_project1)).to eq([new_project1]) }
373
+ specify { expect { association.concat(new_project1) }
374
+ .to change { association.reader.map(&:attributes) }.from([]).to([{'title' => 'Project 1'}]) }
375
+ specify { expect { association.concat(new_project1) }
376
+ .not_to change { user.read_attribute(:projects) } }
377
+
378
+ specify { expect(existing_association.concat(new_project1, invalid_project)).to eq(false) }
379
+ specify { expect { existing_association.concat(new_project1, invalid_project) }
380
+ .to change { existing_user.read_attribute(:projects) }
381
+ .from([{title: 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Project 1'}]) }
382
+ specify { expect { existing_association.concat(new_project1, invalid_project) }
383
+ .to change { existing_association.reader.map(&:attributes) }
384
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Project 1'}, {'title' => nil}]) }
385
+
386
+ specify { expect { existing_association.concat(new_project1, Dummy.new, new_project2) }
387
+ .to raise_error ActiveData::AssociationTypeMismatch }
388
+ specify { expect { existing_association.concat(new_project1, Dummy.new, new_project2) rescue nil }
389
+ .not_to change { existing_user.read_attribute(:projects) } }
390
+ specify { expect { existing_association.concat(new_project1, Dummy.new, new_project2) rescue nil }
391
+ .to change { existing_association.reader.map(&:attributes) }
392
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Project 1'}]) }
393
+
394
+ specify { expect(existing_association.concat(new_project1, new_project2))
395
+ .to eq([existing_user.projects.first, new_project1, new_project2]) }
396
+ specify { expect { existing_association.concat([new_project1, new_project2]) }
397
+ .to change { existing_association.reader.map(&:attributes) }
398
+ .from([{'title' => 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Project 1'}, {'title' => 'Project 2'}]) }
399
+ specify { expect { existing_association.concat([new_project1, new_project2]) }
400
+ .to change { existing_user.read_attribute(:projects) }
401
+ .from([{title: 'Genesis'}]).to([{'title' => 'Genesis'}, {'title' => 'Project 1'}, {'title' => 'Project 2'}]) }
402
+ end
93
403
  end