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,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