granite-form 0.1.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 (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,287 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations::Reflections::ReferencesOne do
4
+ before do
5
+ stub_class(:author, ActiveRecord::Base) do
6
+ scope :name_starts_with_a, -> { name_starts_with('a') }
7
+ scope :name_starts_with, ->(letter) { where("name LIKE '#{letter}%'") }
8
+ end
9
+
10
+ stub_model(:book) do
11
+ include Granite::Form::Model::Associations
12
+
13
+ attribute :title, String
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 Granite::Form::Model::Associations
25
+
26
+ attribute :title, String
27
+ references_one :creator, class_name: 'Author'
28
+ end
29
+ end
30
+ let(:author) { Author.create!(name: 'Rick') }
31
+
32
+ specify do
33
+ expect { book.creator = author }
34
+ .to change { book.creator }.from(nil).to(author)
35
+ end
36
+ specify do
37
+ expect { book.creator = author }
38
+ .to change { book.creator_id }.from(nil).to(author.id)
39
+ end
40
+ end
41
+
42
+ describe ':primary_key' do
43
+ before do
44
+ stub_model(:book) do
45
+ include Granite::Form::Model::Associations
46
+ attribute :author_name, String
47
+ references_one :author, primary_key: 'name'
48
+ end
49
+ end
50
+
51
+ let(:author) { Author.create!(name: 'Rick') }
52
+
53
+ specify do
54
+ expect { book.author_name = author.name }
55
+ .to change { book.author }.from(nil).to(author)
56
+ end
57
+ specify do
58
+ expect { book.author = author }
59
+ .to change { book.author_name }.from(nil).to(author.name)
60
+ end
61
+ end
62
+
63
+ describe ':reference_key' do
64
+ before do
65
+ stub_model(:book) do
66
+ include Granite::Form::Model::Associations
67
+ references_one :author, reference_key: 'identify'
68
+ end
69
+ end
70
+
71
+ let(:author) { Author.create!(name: 'Rick') }
72
+
73
+ specify do
74
+ expect { book.identify = author.id }
75
+ .to change { book.author }.from(nil).to(author)
76
+ end
77
+ specify do
78
+ expect { book.author = author }
79
+ .to change { book.identify }.from(nil).to(author.id)
80
+ end
81
+ end
82
+
83
+ describe ':default' do
84
+ shared_examples_for :persisted_default do |default|
85
+ before do
86
+ stub_model(:book) do
87
+ include Granite::Form::Model::Associations
88
+ references_one :author
89
+ references_one :owner, class_name: 'Author', default: default
90
+ end
91
+ end
92
+
93
+ let(:author) { Author.create! }
94
+ let(:other) { Author.create! }
95
+ let(:book) { Book.new(author: author) }
96
+
97
+ specify { expect(book.owner_id).to eq(author.id) }
98
+ specify { expect(book.owner).to eq(author) }
99
+ specify { expect { book.owner = other }.to change { book.owner_id }.from(author.id).to(other.id) }
100
+ specify { expect { book.owner = other }.to change { book.owner }.from(author).to(other) }
101
+ specify { expect { book.owner_id = other.id }.to change { book.owner_id }.from(author.id).to(other.id) }
102
+ specify { expect { book.owner_id = other.id }.to change { book.owner }.from(author).to(other) }
103
+ specify { expect { book.owner = nil }.to change { book.owner_id }.from(author.id).to(nil) }
104
+ specify { expect { book.owner = nil }.to change { book.owner }.from(author).to(nil) }
105
+ specify { expect { book.owner_id = nil }.not_to change { book.owner_id }.from(author.id) }
106
+ specify { expect { book.owner_id = nil }.not_to change { book.owner }.from(author) }
107
+ specify { expect { book.owner_id = '' }.to change { book.owner_id }.from(author.id).to(nil) }
108
+ specify { expect { book.owner_id = '' }.to change { book.owner }.from(author).to(nil) }
109
+ end
110
+
111
+ it_behaves_like :persisted_default, -> { author.id }
112
+ it_behaves_like :persisted_default, -> { author }
113
+
114
+ shared_examples_for :new_record_default do |default|
115
+ before do
116
+ stub_model(:book) do
117
+ include Granite::Form::Model::Associations
118
+ references_one :author
119
+ references_one :owner, class_name: 'Author', default: default
120
+ end
121
+ end
122
+
123
+ let(:other) { Author.create! }
124
+ let(:book) { Book.new }
125
+
126
+ specify { expect(book.owner_id).to be_nil }
127
+ specify { expect(book.owner).to be_a(Author).and have_attributes(name: 'Author') }
128
+ specify { expect { book.owner = other }.to change { book.owner_id }.from(nil).to(other.id) }
129
+ specify { expect { book.owner = other }.to change { book.owner }.from(instance_of(Author)).to(other) }
130
+ specify { expect { book.owner_id = other.id }.to change { book.owner_id }.from(nil).to(other.id) }
131
+ specify { expect { book.owner_id = other.id }.to change { book.owner }.from(instance_of(Author)).to(other) }
132
+ specify { expect { book.owner = nil }.not_to change { book.owner_id }.from(nil) }
133
+ specify { expect { book.owner = nil }.to change { book.owner }.from(instance_of(Author)).to(nil) }
134
+ specify { expect { book.owner_id = nil }.not_to change { book.owner_id }.from(nil) }
135
+ specify { expect { book.owner_id = nil }.not_to change { book.owner }.from(instance_of(Author)) }
136
+ specify { expect { book.owner_id = '' }.not_to change { book.owner_id }.from(nil) }
137
+ specify { expect { book.owner_id = '' }.to change { book.owner }.from(instance_of(Author)).to(nil) }
138
+ end
139
+
140
+ it_behaves_like :new_record_default, name: 'Author'
141
+ it_behaves_like :new_record_default, -> { Author.new(name: 'Author') }
142
+ end
143
+
144
+ describe 'Book.inspect' do
145
+ specify { expect(Book.inspect).to eq('Book(author: ReferencesOne(Author), title: String, author_id: (reference))') }
146
+ end
147
+
148
+ describe '#scope' do
149
+ before do
150
+ stub_model(:book) do
151
+ include Granite::Form::Model::Associations
152
+ references_one :author, ->(owner) { name_starts_with(owner.letter) }
153
+ attribute :letter, String
154
+ end
155
+ end
156
+
157
+ let(:book) { Book.new(letter: 'a') }
158
+ let!(:author1) { Author.create!(name: 'Rick') }
159
+ let!(:author2) { Author.create!(name: 'Aaron') }
160
+
161
+ specify do
162
+ expect { book.author_id = author1.id }
163
+ .not_to change { book.author }
164
+ end
165
+ specify do
166
+ expect { book.author_id = author2.id }
167
+ .to change { book.author }.from(nil).to(author2)
168
+ end
169
+
170
+ specify do
171
+ expect { book.author = author1 }
172
+ .to change { book.author_id }.from(nil).to(author1.id)
173
+ end
174
+ specify do
175
+ expect { book.author = author2 }
176
+ .to change { book.author_id }.from(nil).to(author2.id)
177
+ end
178
+
179
+ specify do
180
+ expect { book.author = author1 }
181
+ .to change {
182
+ book.association(:author).reload
183
+ book.author_id
184
+ }.from(nil).to(author1.id)
185
+ end
186
+ specify do
187
+ expect { book.author = author2 }
188
+ .to change {
189
+ book.association(:author).reload
190
+ book.author_id
191
+ }.from(nil).to(author2.id)
192
+ end
193
+
194
+ specify do
195
+ expect { book.author = author1 }
196
+ .not_to change {
197
+ book.association(:author).reload
198
+ book.author
199
+ }
200
+ end
201
+ specify do
202
+ expect { book.author = author2 }
203
+ .to change {
204
+ book.association(:author).reload
205
+ book.author
206
+ }.from(nil).to(author2)
207
+ end
208
+
209
+ context do
210
+ let(:book2) { Book.new(letter: 'r') }
211
+
212
+ specify 'scope is not cached too much' do
213
+ expect { book.author_id = author2.id }
214
+ .to change { book.author }.from(nil).to(author2)
215
+ expect { book2.author_id = author1.id }
216
+ .to change { book2.author }.from(nil).to(author1)
217
+ end
218
+ end
219
+ end
220
+
221
+ describe '#author=' do
222
+ let(:author) { Author.create! name: 'Author' }
223
+ specify { expect { book.author = author }.to change { book.author }.from(nil).to(author) }
224
+ specify { expect { book.author = 'string' }.to raise_error Granite::Form::AssociationTypeMismatch }
225
+
226
+ context do
227
+ let(:other) { Author.create! name: 'Other' }
228
+ before { book.author = other }
229
+ specify { expect { book.author = author }.to change { book.author }.from(other).to(author) }
230
+ specify { expect { book.author = author }.to change { book.author_id }.from(other.id).to(author.id) }
231
+ specify { expect { book.author = nil }.to change { book.author }.from(other).to(nil) }
232
+ specify { expect { book.author = nil }.to change { book.author_id }.from(other.id).to(nil) }
233
+ end
234
+
235
+ context 'model not persisted' do
236
+ let(:author) { Author.new }
237
+ specify { expect { book.author = author }.to change { book.author }.from(nil).to(author) }
238
+ specify { expect { book.author = author }.not_to change { book.author_id }.from(nil) }
239
+
240
+ context do
241
+ before { book.author = author }
242
+ specify { expect { author.save! }.to change { book.author_id }.from(nil).to(be_a(Integer)) }
243
+ specify { expect { author.save! }.not_to change { book.author } }
244
+ end
245
+ end
246
+ end
247
+
248
+ describe '#author_id=' do
249
+ let(:author) { Author.create!(name: 'Author') }
250
+ specify { expect { book.author_id = author.id }.to change { book.author_id }.from(nil).to(author.id) }
251
+ specify { expect { book.author_id = author.id }.to change { book.author }.from(nil).to(author) }
252
+ specify { expect { book.author_id = author }.to change { book.author }.from(nil).to(author) }
253
+
254
+ specify { expect { book.author_id = author.id.next.to_s }.to change { book.author_id }.from(nil).to(author.id.next) }
255
+ specify { expect { book.author_id = author.id.next.to_s }.not_to change { book.author }.from(nil) }
256
+
257
+ context do
258
+ let(:other) { Author.create!(name: 'Other') }
259
+ before { book.author = other }
260
+ specify { expect { book.author_id = author.id }.to change { book.author_id }.from(other.id).to(author.id) }
261
+ specify { expect { book.author_id = author.id }.to change { book.author }.from(other).to(author) }
262
+ specify { expect { book.author_id = nil }.to change { book.author_id }.from(other.id).to(nil) }
263
+ specify { expect { book.author_id = nil }.to change { book.author }.from(other).to(nil) }
264
+ end
265
+ end
266
+
267
+ describe '#build_author' do
268
+ specify { expect(book.build_author(name: 'Author')).to be_a(Author) }
269
+ specify { expect(book.build_author(name: 'Author')).not_to be_persisted }
270
+ specify { expect { book.build_author(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
271
+ specify { expect { book.build_author(name: 'Author') }.not_to change { book.author_id }.from(nil) }
272
+ end
273
+
274
+ describe '#create_author' do
275
+ specify { expect(book.create_author(name: 'Author')).to be_a(Author) }
276
+ specify { expect(book.create_author(name: 'Author')).to be_persisted }
277
+ specify { expect { book.create_author(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
278
+ specify { expect { book.create_author(name: 'Author') }.to change { book.author_id }.from(nil).to(be_a(Integer)) }
279
+ end
280
+
281
+ describe '#create_author!' do
282
+ specify { expect(book.create_author!(name: 'Author')).to be_a(Author) }
283
+ specify { expect(book.create_author!(name: 'Author')).to be_persisted }
284
+ specify { expect { book.create_author!(name: 'Author') }.to change { book.author }.from(nil).to(an_instance_of(Author)) }
285
+ specify { expect { book.create_author!(name: 'Author') }.to change { book.author_id }.from(nil).to(be_a(Integer)) }
286
+ end
287
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations::Validations do
4
+ before do
5
+ stub_model(:project) do
6
+ include Granite::Form::Model::Lifecycle
7
+ include Granite::Form::Model::Associations
8
+ include Granite::Form::Model::Associations::Validations
9
+
10
+ attribute :title, String
11
+ validates :title, presence: true
12
+
13
+ embeds_one :author do
14
+ attribute :name, String
15
+ validates :name, presence: true
16
+ end
17
+ end
18
+
19
+ stub_model(:profile) do
20
+ include Granite::Form::Model::Lifecycle
21
+
22
+ attribute :first_name, String
23
+ attribute :last_name, String
24
+ validates :first_name, presence: true
25
+ end
26
+
27
+ stub_model(:user) do
28
+ include Granite::Form::Model::Associations
29
+ include Granite::Form::Model::Associations::Validations
30
+
31
+ attribute :login, String
32
+ validates :login, presence: true
33
+
34
+ embeds_one :profile, validate: false
35
+ embeds_many :projects
36
+ end
37
+ end
38
+
39
+ describe '#validate' do
40
+ let(:profile) { Profile.new first_name: 'Name' }
41
+ let(:project) { Project.new title: 'Project' }
42
+ let(:projects) { [project] }
43
+ let(:user) { User.new(login: 'Login', profile: profile, projects: projects) }
44
+ let(:author_attributes) { {name: 'Author'} }
45
+ before { project.build_author(author_attributes) }
46
+
47
+ specify { expect(user.validate).to eq(true) }
48
+ specify { expect { user.validate }.not_to change { user.errors.messages } }
49
+
50
+ context do
51
+ let(:author_attributes) { {} }
52
+
53
+ specify { expect(user.validate).to eq(false) }
54
+ specify do
55
+ expect { user.validate }.to change { user.errors.messages }
56
+ .to('projects.0.author.name': ["can't be blank"])
57
+ end
58
+ end
59
+
60
+ context do
61
+ let(:profile) { Profile.new }
62
+
63
+ specify { expect(user.validate).to eq(true) }
64
+ specify { expect { user.validate }.not_to change { user.errors.messages } }
65
+ end
66
+
67
+ context do
68
+ let(:projects) { [project, Project.new] }
69
+
70
+ specify { expect(user.validate).to eq(false) }
71
+ specify do
72
+ expect { user.validate }.to change { user.errors.messages }
73
+ .to('projects.1.title': ["can't be blank"])
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#validate_ancestry, #valid_ancestry?, #invalid_ancestry?' do
79
+ let(:profile) { Profile.new first_name: 'Name' }
80
+ let(:project) { Project.new title: 'Project' }
81
+ let(:projects) { [project] }
82
+ let(:user) { User.new(login: 'Login', profile: profile, projects: projects) }
83
+ let(:author_attributes) { {name: 'Author'} }
84
+ before { project.build_author(author_attributes) }
85
+
86
+ specify { expect(user.validate_ancestry).to eq(true) }
87
+ specify { expect(user.validate_ancestry!).to eq(true) }
88
+ specify { expect { user.validate_ancestry! }.not_to raise_error }
89
+ specify { expect(user.valid_ancestry?).to eq(true) }
90
+ specify { expect(user.invalid_ancestry?).to eq(false) }
91
+ specify { expect { user.validate_ancestry }.not_to change { user.errors.messages } }
92
+
93
+ context do
94
+ let(:author_attributes) { {} }
95
+ specify { expect(user.validate_ancestry).to eq(false) }
96
+ specify { expect { user.validate_ancestry! }.to raise_error Granite::Form::ValidationError }
97
+ specify { expect(user.valid_ancestry?).to eq(false) }
98
+ specify { expect(user.invalid_ancestry?).to eq(true) }
99
+ specify do
100
+ expect { user.validate_ancestry }.to change { user.errors.messages }
101
+ .to('projects.0.author.name': ["can't be blank"])
102
+ end
103
+ end
104
+
105
+ context do
106
+ let(:profile) { Profile.new }
107
+ specify { expect(user.validate_ancestry).to eq(false) }
108
+ specify { expect { user.validate_ancestry! }.to raise_error Granite::Form::ValidationError }
109
+ specify { expect(user.valid_ancestry?).to eq(false) }
110
+ specify { expect(user.invalid_ancestry?).to eq(true) }
111
+ specify do
112
+ expect { user.validate_ancestry }.to change { user.errors.messages }
113
+ .to('profile.first_name': ["can't be blank"])
114
+ end
115
+ end
116
+
117
+ context do
118
+ let(:projects) { [project, Project.new] }
119
+ specify { expect(user.validate_ancestry).to eq(false) }
120
+ specify { expect { user.validate_ancestry! }.to raise_error Granite::Form::ValidationError }
121
+ specify { expect(user.valid_ancestry?).to eq(false) }
122
+ specify { expect(user.invalid_ancestry?).to eq(true) }
123
+ specify do
124
+ expect { user.validate_ancestry }.to change { user.errors.messages }
125
+ .to('projects.1.title': ["can't be blank"])
126
+ end
127
+
128
+ context do
129
+ before { user.update(login: '') }
130
+ specify do
131
+ expect { user.validate_ancestry }.to change { user.errors.messages }
132
+ .to('projects.1.title': ["can't be blank"], login: ["can't be blank"])
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Associations do
4
+ context do
5
+ before do
6
+ stub_model(:nobody) do
7
+ include Granite::Form::Model::Associations
8
+ end
9
+ stub_model(:project) do
10
+ include Granite::Form::Model::Lifecycle
11
+ end
12
+ stub_model(:user, Nobody) do
13
+ include Granite::Form::Model::Associations
14
+ embeds_many :projects
15
+ end
16
+ stub_model(:manager, Nobody) do
17
+ include Granite::Form::Model::Associations
18
+ embeds_one :managed_project, class_name: 'Project'
19
+ end
20
+ stub_model(:admin, User) do
21
+ include Granite::Form::Model::Associations
22
+ embeds_many :admin_projects, class_name: 'Project'
23
+
24
+ alias_association :own_projects, :admin_projects
25
+ end
26
+ end
27
+
28
+ describe '#reflections' do
29
+ specify { expect(Nobody.reflections.keys).to eq([]) }
30
+ specify { expect(User.reflections.keys).to eq([:projects]) }
31
+ specify { expect(Manager.reflections.keys).to eq([:managed_project]) }
32
+ specify { expect(Admin.reflections.keys).to eq(%i[projects admin_projects]) }
33
+ end
34
+
35
+ describe '#reflect_on_association' do
36
+ specify { expect(Nobody.reflect_on_association(:blabla)).to be_nil }
37
+ specify { expect(Admin.reflect_on_association('projects')).to be_a Granite::Form::Model::Associations::Reflections::EmbedsMany }
38
+ specify { expect(Admin.reflect_on_association('own_projects').name).to eq(:admin_projects) }
39
+ specify { expect(Manager.reflect_on_association(:managed_project)).to be_a Granite::Form::Model::Associations::Reflections::EmbedsOne }
40
+ end
41
+ end
42
+
43
+ context 'class determine errors' do
44
+ specify do
45
+ expect do
46
+ stub_model do
47
+ include Granite::Form::Model::Associations
48
+
49
+ embeds_one :author, class_name: 'Borogoves'
50
+ end.reflect_on_association(:author).data_source end.to raise_error NameError
51
+ end
52
+
53
+ specify do
54
+ expect do
55
+ stub_model(:user) do
56
+ include Granite::Form::Model::Associations
57
+
58
+ embeds_many :projects, class_name: 'Borogoves' do
59
+ attribute :title
60
+ end
61
+ end.reflect_on_association(:projects).data_source end.to raise_error NameError
62
+ end
63
+ end
64
+
65
+ context do
66
+ before do
67
+ stub_model(:project) do
68
+ include Granite::Form::Model::Lifecycle
69
+ include Granite::Form::Model::Associations
70
+
71
+ attribute :title, String
72
+
73
+ validates :title, presence: true
74
+
75
+ embeds_one :author do
76
+ attribute :name, String
77
+
78
+ validates :name, presence: true
79
+ end
80
+ end
81
+
82
+ stub_model(:profile) do
83
+ include Granite::Form::Model::Lifecycle
84
+
85
+ attribute :first_name, String
86
+ attribute :last_name, String
87
+
88
+ validates :first_name, presence: true
89
+ end
90
+
91
+ stub_model(:user) do
92
+ include Granite::Form::Model::Associations
93
+
94
+ attribute :login, Object
95
+
96
+ validates :login, presence: true
97
+
98
+ embeds_one :profile
99
+ embeds_many :projects
100
+
101
+ alias_association :my_profile, :profile
102
+ end
103
+ end
104
+
105
+ let(:user) { User.new }
106
+
107
+ specify { expect(user.projects).to eq([]) }
108
+ specify { expect(user.profile).to be_nil }
109
+
110
+ describe '.inspect' do
111
+ specify { expect(User.inspect).to eq('User(profile: EmbedsOne(Profile), projects: EmbedsMany(Project), login: Object)') }
112
+ end
113
+
114
+ describe '.association_names' do
115
+ specify { expect(User.association_names).to eq(%i[profile projects]) }
116
+ end
117
+
118
+ describe '#inspect' do
119
+ let(:profile) { Profile.new first_name: 'Name' }
120
+ let(:project) { Project.new title: 'Project' }
121
+ specify do
122
+ expect(User.new(login: 'Login', profile: profile, projects: [project]).inspect)
123
+ .to eq('#<User profile: #<EmbedsOne #<Profile first_name: "Name", last_name: nil>>, projects: #<EmbedsMany [#<Project author: #<EmbedsOne nil>, title: "P...]>, login: "Login">')
124
+ end
125
+ end
126
+
127
+ describe '#==' do
128
+ let(:project) { Project.new title: 'Project' }
129
+ let(:other) { Project.new title: 'Other' }
130
+
131
+ specify { expect(User.new(projects: [project])).to eq(User.new(projects: [project])) }
132
+ specify { expect(User.new(projects: [project])).not_to eq(User.new(projects: [other])) }
133
+ specify { expect(User.new(projects: [project])).not_to eq(User.new) }
134
+
135
+ specify { expect(User.new(projects: [project])).to eql(User.new(projects: [project])) }
136
+ specify { expect(User.new(projects: [project])).not_to eql(User.new(projects: [other])) }
137
+ specify { expect(User.new(projects: [project])).not_to eql(User.new) }
138
+
139
+ context do
140
+ before { User.send(:include, Granite::Form::Model::Primary) }
141
+ let(:user) { User.new(projects: [project]) }
142
+
143
+ specify { expect(user).to eq(user.clone.tap { |b| b.projects(author: project) }) }
144
+ specify { expect(user).to eq(user.clone.tap { |b| b.projects(author: other) }) }
145
+
146
+ specify { expect(user).to eql(user.clone.tap { |b| b.projects(author: project) }) }
147
+ specify { expect(user).to eql(user.clone.tap { |b| b.projects(author: other) }) }
148
+ end
149
+ end
150
+
151
+ describe '#association' do
152
+ specify { expect(user.association(:projects)).to be_a(Granite::Form::Model::Associations::EmbedsMany) }
153
+ specify { expect(user.association(:profile)).to be_a(Granite::Form::Model::Associations::EmbedsOne) }
154
+ specify { expect(user.association(:blabla)).to be_nil }
155
+ specify { expect(user.association('my_profile').reflection.name).to eq(:profile) }
156
+ specify { expect(user.association('my_profile')).to equal(user.association(:profile)) }
157
+ end
158
+
159
+ describe '#association_names' do
160
+ specify { expect(user.association_names).to eq(%i[profile projects]) }
161
+ end
162
+
163
+ describe '#apply_association_changes!' do
164
+ let(:profile) { Profile.new first_name: 'Name' }
165
+ let(:project) { Project.new title: 'Project' }
166
+ let(:user) { User.new(profile: profile, projects: [project]) }
167
+ before { project.build_author(name: 'Author') }
168
+
169
+ specify do
170
+ expect { user.apply_association_changes! }.to change { user.attributes['profile'] }
171
+ .from(nil).to('first_name' => 'Name', 'last_name' => nil)
172
+ end
173
+ specify do
174
+ expect { user.apply_association_changes! }.to change { user.attributes['projects'] }
175
+ .from(nil).to([{'title' => 'Project', 'author' => {'name' => 'Author'}}])
176
+ end
177
+
178
+ context do
179
+ let(:project) { Project.new }
180
+ specify { expect { user.apply_association_changes! }.to raise_error Granite::Form::AssociationChangesNotApplied }
181
+ end
182
+ end
183
+
184
+ describe '#instantiate' do
185
+ before { User.send(:include, Granite::Form::Model::Persistence) }
186
+ let(:profile) { Profile.new first_name: 'Name' }
187
+ let(:project) { Project.new title: 'Project' }
188
+ let(:user) { User.new(profile: profile, projects: [project]) }
189
+ before { project.build_author(name: 'Author') }
190
+
191
+ specify { expect(User.instantiate(JSON.parse(user.to_json))).to eq(user) }
192
+ specify do
193
+ expect(User.instantiate(JSON.parse(user.to_json))
194
+ .tap { |u| u.projects.first.author.name = 'Other' }).not_to eq(user)
195
+ end
196
+ end
197
+ end
198
+ end