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,208 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::Reflections::ReferencesOne do
5
+ before do
6
+ stub_class(:author, ActiveRecord::Base) do
7
+ scope :name_starts_with_a, -> { where('name LIKE "a%"') }
8
+ end
9
+
10
+ stub_model(:book) do
11
+ include ActiveData::Model::Associations
12
+
13
+ attribute :title
14
+ references_one :author
15
+ end
16
+ end
17
+ let(:book) { Book.new }
18
+
19
+ specify { expect(book.author).to be_nil }
20
+
21
+ context ':class_name' do
22
+ before do
23
+ stub_model(:book) do
24
+ include ActiveData::Model::Associations
25
+
26
+ attribute :title
27
+ references_one :creator, class_name: 'Author'
28
+ end
29
+ end
30
+ let(:author) { Author.create!(name: 'Rick') }
31
+
32
+ specify { expect { book.creator = author }
33
+ .to change { book.creator }.from(nil).to(author) }
34
+ specify { expect { book.creator = author }
35
+ .to change { book.creator_id }.from(nil).to(author.id) }
36
+ end
37
+
38
+ describe ':primary_key' do
39
+ before do
40
+ stub_model(:book) do
41
+ include ActiveData::Model::Associations
42
+ attribute :author_name, String
43
+ references_one :author, primary_key: 'name'
44
+ end
45
+ end
46
+
47
+ let(:author) { Author.create!(name: 'Rick') }
48
+
49
+ specify { expect { book.author_name = author.name }
50
+ .to change { book.author }.from(nil).to(author) }
51
+ specify { expect { book.author = author }
52
+ .to change { book.author_name }.from(nil).to(author.name) }
53
+ end
54
+
55
+ describe ':reference_key' do
56
+ before do
57
+ stub_model(:book) do
58
+ include ActiveData::Model::Associations
59
+ references_one :author, reference_key: 'identify'
60
+ end
61
+ end
62
+
63
+ let(:author) { Author.create!(name: 'Rick') }
64
+
65
+ specify { expect { book.identify = author.id }
66
+ .to change { book.author }.from(nil).to(author) }
67
+ specify { expect { book.author = author }
68
+ .to change { book.identify }.from(nil).to(author.id) }
69
+ end
70
+
71
+ describe ':default' do
72
+ shared_examples_for :persisted_default do |default|
73
+ before do
74
+ stub_model(:book) do
75
+ include ActiveData::Model::Associations
76
+ references_one :author
77
+ references_one :owner, class_name: 'Author', default: default
78
+ end
79
+ end
80
+
81
+ let(:author) { Author.create! }
82
+ let(:other) { Author.create! }
83
+ let(:book) { Book.new(author: author) }
84
+
85
+ specify { expect(book.owner_id).to eq(author.id) }
86
+ specify { expect(book.owner).to eq(author) }
87
+ specify { expect { book.owner = other }.to change { book.owner_id }.from(author.id).to(other.id) }
88
+ specify { expect { book.owner = other }.to change { book.owner }.from(author).to(other) }
89
+ specify { expect { book.owner_id = other.id }.to change { book.owner_id }.from(author.id).to(other.id) }
90
+ specify { expect { book.owner_id = other.id }.to change { book.owner }.from(author).to(other) }
91
+ specify { expect { book.owner = nil }.to change { book.owner_id }.from(author.id).to(nil) }
92
+ specify { expect { book.owner = nil }.to change { book.owner }.from(author).to(nil) }
93
+ specify { expect { book.owner_id = nil }.not_to change { book.owner_id }.from(author.id) }
94
+ specify { expect { book.owner_id = nil }.not_to change { book.owner }.from(author) }
95
+ specify { expect { book.owner_id = '' }.to change { book.owner_id }.from(author.id).to(nil) }
96
+ specify { expect { book.owner_id = '' }.to change { book.owner }.from(author).to(nil) }
97
+ end
98
+
99
+ it_behaves_like :persisted_default, -> { author.id }
100
+ it_behaves_like :persisted_default, -> { author }
101
+
102
+ shared_examples_for :new_record_default do |default|
103
+ before do
104
+ stub_model(:book) do
105
+ include ActiveData::Model::Associations
106
+ references_one :author
107
+ references_one :owner, class_name: 'Author', default: default
108
+ end
109
+ end
110
+
111
+ let(:other) { Author.create! }
112
+ let(:book) { Book.new }
113
+
114
+ specify { expect(book.owner_id).to be_nil }
115
+ specify { expect(book.owner).to be_a(Author).and have_attributes(name: 'Author') }
116
+ specify { expect { book.owner = other }.to change { book.owner_id }.from(nil).to(other.id) }
117
+ specify { expect { book.owner = other }.to change { book.owner }.from(instance_of(Author)).to(other) }
118
+ specify { expect { book.owner_id = other.id }.to change { book.owner_id }.from(nil).to(other.id) }
119
+ specify { expect { book.owner_id = other.id }.to change { book.owner }.from(instance_of(Author)).to(other) }
120
+ specify { expect { book.owner = nil }.not_to change { book.owner_id }.from(nil) }
121
+ specify { expect { book.owner = nil }.to change { book.owner }.from(instance_of(Author)).to(nil) }
122
+ specify { expect { book.owner_id = nil }.not_to change { book.owner_id }.from(nil) }
123
+ specify { expect { book.owner_id = nil }.not_to change { book.owner }.from(instance_of(Author)) }
124
+ specify { expect { book.owner_id = '' }.not_to change { book.owner_id }.from(nil) }
125
+ specify { expect { book.owner_id = '' }.to change { book.owner }.from(instance_of(Author)).to(nil) }
126
+ end
127
+
128
+ it_behaves_like :new_record_default, name: 'Author'
129
+ it_behaves_like :new_record_default, -> { Author.new(name: 'Author') }
130
+ end
131
+
132
+ describe '#scope' do
133
+ before do
134
+ stub_model(:book) do
135
+ include ActiveData::Model::Associations
136
+ references_one :author, -> { name_starts_with_a }
137
+ end
138
+ end
139
+
140
+ let!(:author1) { Author.create!(name: 'Rick') }
141
+ let!(:author2) { Author.create!(name: 'Aaron') }
142
+
143
+ specify { expect { book.author_id = author1.id }
144
+ .not_to change { book.author } }
145
+ specify { expect { book.author_id = author2.id }
146
+ .to change { book.author }.from(nil).to(author2) }
147
+
148
+ specify { expect { book.author = author1 }
149
+ .to change { book.author_id }.from(nil).to(author1.id) }
150
+ specify { expect { book.author = author2 }
151
+ .to change { book.author_id }.from(nil).to(author2.id) }
152
+
153
+ specify { expect { book.author = author1 }
154
+ .to change { book.association(:author).reload; book.author_id }.from(nil).to(author1.id) }
155
+ specify { expect { book.author = author2 }
156
+ .to change { book.association(:author).reload; book.author_id }.from(nil).to(author2.id) }
157
+
158
+ specify { expect { book.author = author1 }
159
+ .not_to change { book.association(:author).reload; book.author } }
160
+ specify { expect { book.author = author2 }
161
+ .to change { book.association(:author).reload; book.author }.from(nil).to(author2) }
162
+ end
163
+
164
+ describe '#author=' do
165
+ let(:author) { Author.create! name: 'Author' }
166
+ specify { expect { book.author = author }.to change { book.author }.from(nil).to(author) }
167
+ specify { expect { book.author = 'string' }.to raise_error ActiveData::AssociationTypeMismatch }
168
+
169
+ context do
170
+ let(:other) { Author.create! name: 'Other' }
171
+ before { book.author = other }
172
+ specify { expect { book.author = author }.to change { book.author }.from(other).to(author) }
173
+ specify { expect { book.author = author }.to change { book.author_id }.from(other.id).to(author.id) }
174
+ specify { expect { book.author = nil }.to change { book.author }.from(other).to(nil) }
175
+ specify { expect { book.author = nil }.to change { book.author_id }.from(other.id).to(nil) }
176
+ end
177
+
178
+ context 'model not persisted' do
179
+ let(:author) { Author.new }
180
+ specify { expect { book.author = author }.to change { book.author }.from(nil).to(author) }
181
+ specify { expect { book.author = author }.not_to change { book.author_id }.from(nil) }
182
+
183
+ context do
184
+ before { book.author = author }
185
+ specify { expect { author.save! }.to change { book.author_id }.from(nil).to(an_instance_of(Fixnum)) }
186
+ specify { expect { author.save! }.not_to change { book.author } }
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#author_id=' do
192
+ let(:author) { Author.create!(name: 'Author') }
193
+ specify { expect { book.author_id = author.id }.to change { book.author_id }.from(nil).to(author.id) }
194
+ specify { expect { book.author_id = author.id }.to change { book.author }.from(nil).to(author) }
195
+
196
+ specify { expect { book.author_id = author.id.next.to_s }.to change { book.author_id }.from(nil).to(author.id.next) }
197
+ specify { expect { book.author_id = author.id.next.to_s }.not_to change { book.author }.from(nil) }
198
+
199
+ context do
200
+ let(:other) { Author.create!(name: 'Other') }
201
+ before { book.author = other }
202
+ specify { expect { book.author_id = author.id }.to change { book.author_id }.from(other.id).to(author.id) }
203
+ specify { expect { book.author_id = author.id }.to change { book.author }.from(other).to(author) }
204
+ specify { expect { book.author_id = nil }.to change { book.author_id }.from(other.id).to(nil) }
205
+ specify { expect { book.author_id = nil }.to change { book.author }.from(other).to(nil) }
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveData::Model::Associations::Validations do
4
+ before do
5
+ stub_model(:project) do
6
+ include ActiveData::Model::Lifecycle
7
+ include ActiveData::Model::Associations
8
+
9
+ attribute :title, String
10
+ validates :title, presence: true
11
+
12
+ embeds_one :author do
13
+ attribute :name, String
14
+ validates :name, presence: true
15
+ end
16
+ end
17
+
18
+ stub_model(:profile) do
19
+ include ActiveData::Model::Lifecycle
20
+
21
+ attribute :first_name, String
22
+ attribute :last_name, String
23
+ validates :first_name, presence: true
24
+ end
25
+
26
+ stub_model(:user) do
27
+ include ActiveData::Model::Associations
28
+
29
+ attribute :login, String
30
+ validates :login, presence: true
31
+
32
+ embeds_one :profile, validate: false
33
+ embeds_many :projects
34
+ end
35
+ end
36
+
37
+ describe '#validate' do
38
+ let(:profile) { Profile.new first_name: 'Name' }
39
+ let(:project) { Project.new title: 'Project' }
40
+ let(:projects) { [project] }
41
+ let(:user) { User.new(login: 'Login', profile: profile, projects: projects) }
42
+ let(:author_attributes) { { name: 'Author' } }
43
+ before { project.build_author(author_attributes) }
44
+
45
+ specify { expect(user.validate).to eq(true) }
46
+ specify { expect { user.validate }.not_to change { user.errors.messages } }
47
+
48
+ context do
49
+ let(:author_attributes) { {} }
50
+
51
+ specify { expect(user.validate).to eq(false) }
52
+ specify { expect { user.validate }.to change { user.errors.messages }
53
+ .to(:'projects.0.author.name' => ["can't be blank"]) }
54
+ end
55
+
56
+ context do
57
+ let(:profile) { Profile.new }
58
+
59
+ specify { expect(user.validate).to eq(true) }
60
+ specify { expect { user.validate }.not_to change { user.errors.messages } }
61
+ end
62
+
63
+ context do
64
+ let(:projects) { [project, Project.new] }
65
+
66
+ specify { expect(user.validate).to eq(false) }
67
+ specify { expect { user.validate }.to change { user.errors.messages }
68
+ .to(:'projects.1.title' => ["can't be blank"]) }
69
+ end
70
+ end
71
+
72
+ describe '#validate_ancestry, #valid_ancestry?, #invalid_ancestry?' do
73
+ let(:profile) { Profile.new first_name: 'Name' }
74
+ let(:project) { Project.new title: 'Project' }
75
+ let(:projects) { [project] }
76
+ let(:user) { User.new(login: 'Login', profile: profile, projects: projects) }
77
+ let(:author_attributes) { { name: 'Author' } }
78
+ before { project.build_author(author_attributes) }
79
+
80
+ specify { expect(user.validate_ancestry).to eq(true) }
81
+ specify { expect(user.validate_ancestry!).to eq(true) }
82
+ specify { expect { user.validate_ancestry! }.not_to raise_error }
83
+ specify { expect(user.valid_ancestry?).to eq(true) }
84
+ specify { expect(user.invalid_ancestry?).to eq(false) }
85
+ specify { expect { user.validate_ancestry }.not_to change { user.errors.messages } }
86
+
87
+ context do
88
+ let(:author_attributes) { {} }
89
+ specify { expect(user.validate_ancestry).to eq(false) }
90
+ specify { expect { user.validate_ancestry! }.to raise_error ActiveData::ValidationError }
91
+ specify { expect(user.valid_ancestry?).to eq(false) }
92
+ specify { expect(user.invalid_ancestry?).to eq(true) }
93
+ specify { expect { user.validate_ancestry }.to change { user.errors.messages }
94
+ .to(:'projects.0.author.name' => ["can't be blank"]) }
95
+ end
96
+
97
+ context do
98
+ let(:profile) { Profile.new }
99
+ specify { expect(user.validate_ancestry).to eq(false) }
100
+ specify { expect { user.validate_ancestry! }.to raise_error ActiveData::ValidationError }
101
+ specify { expect(user.valid_ancestry?).to eq(false) }
102
+ specify { expect(user.invalid_ancestry?).to eq(true) }
103
+ specify { expect { user.validate_ancestry }.to change { user.errors.messages }
104
+ .to(:'profile.first_name' => ["can't be blank"]) }
105
+ end
106
+
107
+ context do
108
+ let(:projects) { [project, Project.new] }
109
+ specify { expect(user.validate_ancestry).to eq(false) }
110
+ specify { expect { user.validate_ancestry! }.to raise_error ActiveData::ValidationError }
111
+ specify { expect(user.valid_ancestry?).to eq(false) }
112
+ specify { expect(user.invalid_ancestry?).to eq(true) }
113
+ specify { expect { user.validate_ancestry }.to change { user.errors.messages }
114
+ .to(:'projects.1.title' => ["can't be blank"]) }
115
+
116
+ context do
117
+ before { user.update(login: '') }
118
+ specify { expect { user.validate_ancestry }.to change { user.errors.messages }
119
+ .to(:'projects.1.title' => ["can't be blank"], login: ["can't be blank"]) }
120
+ end
121
+ end
122
+ end
123
+
124
+ context 'represent attributes' do
125
+ before do
126
+ stub_class(:author, ActiveRecord::Base) do
127
+ validates :name, presence: true
128
+
129
+ # Emulate Active Record association auto save error.
130
+ def errors
131
+ super.tap do |errors|
132
+ errors.add(:'user.email', 'is invalid') if errors[:'user.email'].empty?
133
+ end
134
+ end
135
+ end
136
+
137
+ stub_model(:post) do
138
+ include ActiveData::Model::Associations
139
+
140
+ references_one :author
141
+ represents :name, of: :author
142
+ represents :email, of: :author
143
+ end
144
+ end
145
+
146
+ let(:post) { Post.new(author: Author.new) }
147
+
148
+ specify { expect { post.validate_ancestry }.to change { post.errors.messages }
149
+ .to(hash_including(:'author.user.email' => ['is invalid'], name: ["can't be blank"])) }
150
+ specify { expect { post.validate }.to change { post.errors.messages }
151
+ .to(hash_including(:'author.user.email' => ['is invalid'], name: ["can't be blank"])) }
152
+ end
153
+ end
@@ -0,0 +1,189 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations do
5
+ context do
6
+ before do
7
+ stub_model(:nobody) do
8
+ include ActiveData::Model::Associations
9
+ end
10
+ stub_model(:project) do
11
+ include ActiveData::Model::Lifecycle
12
+ end
13
+ stub_model(:user, Nobody) do
14
+ include ActiveData::Model::Associations
15
+ embeds_many :projects
16
+ end
17
+ stub_model(:manager, Nobody) do
18
+ include ActiveData::Model::Associations
19
+ embeds_one :managed_project, class_name: 'Project'
20
+ end
21
+ stub_model(:admin, User) do
22
+ include ActiveData::Model::Associations
23
+ embeds_many :admin_projects, class_name: 'Project'
24
+
25
+ alias_association :own_projects, :admin_projects
26
+ end
27
+ end
28
+
29
+ describe '#reflections' do
30
+ specify { expect(Nobody.reflections.keys).to eq([]) }
31
+ specify { expect(User.reflections.keys).to eq([:projects]) }
32
+ specify { expect(Manager.reflections.keys).to eq([:managed_project]) }
33
+ specify { expect(Admin.reflections.keys).to eq([:projects, :admin_projects]) }
34
+ end
35
+
36
+ describe '#reflect_on_association' do
37
+ specify { expect(Nobody.reflect_on_association(:blabla)).to be_nil }
38
+ specify { expect(Admin.reflect_on_association('projects')).to be_a ActiveData::Model::Associations::Reflections::EmbedsMany }
39
+ specify { expect(Admin.reflect_on_association('own_projects').name).to eq(:admin_projects) }
40
+ specify { expect(Manager.reflect_on_association(:managed_project)).to be_a ActiveData::Model::Associations::Reflections::EmbedsOne }
41
+ end
42
+ end
43
+
44
+ context 'class determine errors' do
45
+ specify do
46
+ expect { stub_model do
47
+ include ActiveData::Model::Associations
48
+
49
+ embeds_one :author, class_name: 'Borogoves'
50
+ end.reflect_on_association(:author).klass }.to raise_error NameError
51
+ end
52
+
53
+ specify do
54
+ expect { stub_model(:user) do
55
+ include ActiveData::Model::Associations
56
+
57
+ embeds_many :projects, class_name: 'Borogoves' do
58
+ attribute :title
59
+ end
60
+ end.reflect_on_association(:projects).klass }.to raise_error NameError
61
+ end
62
+ end
63
+
64
+ context do
65
+ before do
66
+ stub_model(:project) do
67
+ include ActiveData::Model::Lifecycle
68
+ include ActiveData::Model::Associations
69
+
70
+ attribute :title, String
71
+
72
+ validates :title, presence: true
73
+
74
+ embeds_one :author do
75
+ attribute :name, String
76
+
77
+ validates :name, presence: true
78
+ end
79
+ end
80
+
81
+ stub_model(:profile) do
82
+ include ActiveData::Model::Lifecycle
83
+
84
+ attribute :first_name, String
85
+ attribute :last_name, String
86
+
87
+ validates :first_name, presence: true
88
+ end
89
+
90
+ stub_model(:user) do
91
+ include ActiveData::Model::Associations
92
+
93
+ attribute :login, Object
94
+
95
+ validates :login, presence: true
96
+
97
+ embeds_one :profile
98
+ embeds_many :projects
99
+
100
+ alias_association :my_profile, :profile
101
+ end
102
+ end
103
+
104
+ let(:user) { User.new }
105
+
106
+ its(:projects) { should = [] }
107
+ its(:profile) { should = nil }
108
+
109
+ describe '.inspect' do
110
+ specify { expect(User.inspect).to eq('User(profile: EmbedsOne(Profile), projects: EmbedsMany(Project), login: Object)') }
111
+ end
112
+
113
+ describe '.association_names' do
114
+ specify { expect(User.association_names).to eq([:profile, :projects]) }
115
+ end
116
+
117
+ describe '#inspect' do
118
+ let(:profile) { Profile.new first_name: 'Name' }
119
+ let(:project) { Project.new title: 'Project' }
120
+ specify { expect(User.new(login: 'Login', profile: profile, projects: [project]).inspect)
121
+ .to eq('#<User profile: #<EmbedsOne #<Profile first_name: "Name", last_name: nil>>, projects: #<EmbedsMany [#<Project author: #<EmbedsOne nil>, title: "P...]>, login: "Login">') }
122
+ end
123
+
124
+ describe '#==' do
125
+ let(:project) { Project.new title: 'Project' }
126
+ let(:other) { Project.new title: 'Other' }
127
+
128
+ specify { expect(User.new(projects: [project])).to eq(User.new(projects: [project])) }
129
+ specify { expect(User.new(projects: [project])).not_to eq(User.new(projects: [other])) }
130
+ specify { expect(User.new(projects: [project])).not_to eq(User.new) }
131
+
132
+ specify { expect(User.new(projects: [project])).to eql(User.new(projects: [project])) }
133
+ specify { expect(User.new(projects: [project])).not_to eql(User.new(projects: [other])) }
134
+ specify { expect(User.new(projects: [project])).not_to eql(User.new) }
135
+
136
+ context do
137
+ before { User.send(:include, ActiveData::Model::Primary) }
138
+ let(:user) { User.new(projects: [project]) }
139
+
140
+ specify { expect(user).to eq(user.clone.tap { |b| b.projects(author: project) }) }
141
+ specify { expect(user).to eq(user.clone.tap { |b| b.projects(author: other) }) }
142
+
143
+ specify { expect(user).to eql(user.clone.tap { |b| b.projects(author: project) }) }
144
+ specify { expect(user).to eql(user.clone.tap { |b| b.projects(author: other) }) }
145
+ end
146
+ end
147
+
148
+ describe '#association' do
149
+ specify { expect(user.association(:projects)).to be_a(ActiveData::Model::Associations::EmbedsMany) }
150
+ specify { expect(user.association(:profile)).to be_a(ActiveData::Model::Associations::EmbedsOne) }
151
+ specify { expect(user.association(:blabla)).to be_nil }
152
+ specify { expect(user.association('my_profile').reflection.name).to eq(:profile) }
153
+ specify { expect(user.association('my_profile')).to equal(user.association(:profile)) }
154
+ end
155
+
156
+ describe '#association_names' do
157
+ specify { expect(user.association_names).to eq([:profile, :projects]) }
158
+ end
159
+
160
+ describe '#apply_association_changes!' do
161
+ let(:profile) { Profile.new first_name: 'Name' }
162
+ let(:project) { Project.new title: 'Project' }
163
+ let(:user) { User.new(profile: profile, projects: [project]) }
164
+ before { project.build_author(name: 'Author') }
165
+
166
+ specify { expect { user.apply_association_changes! }.to change { user.attributes['profile'] }
167
+ .from(nil).to('first_name' => 'Name', 'last_name' => nil) }
168
+ specify { expect { user.apply_association_changes! }.to change { user.attributes['projects'] }
169
+ .from(nil).to([{ 'title' => 'Project', 'author' => { 'name' => 'Author' } }]) }
170
+
171
+ context do
172
+ let(:project) { Project.new }
173
+ specify { expect { user.apply_association_changes! }.to raise_error ActiveData::AssociationChangesNotApplied }
174
+ end
175
+ end
176
+
177
+ describe '#instantiate' do
178
+ before { User.send(:include, ActiveData::Model::Persistence) }
179
+ let(:profile) { Profile.new first_name: 'Name' }
180
+ let(:project) { Project.new title: 'Project' }
181
+ let(:user) { User.new(profile: profile, projects: [project]) }
182
+ before { project.build_author(name: 'Author') }
183
+
184
+ specify { expect(User.instantiate(JSON.parse(user.to_json))).to eq(user) }
185
+ specify { expect(User.instantiate(JSON.parse(user.to_json))
186
+ .tap { |u| u.projects.first.author.name = 'Other' }).not_to eq(user) }
187
+ end
188
+ end
189
+ end