guacamole 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1 -1
  3. data/.travis.yml +16 -15
  4. data/CHANGELOG.md +16 -0
  5. data/GOALS.md +8 -0
  6. data/Guardfile +4 -0
  7. data/README.md +21 -81
  8. data/guacamole.gemspec +3 -3
  9. data/lib/guacamole.rb +1 -0
  10. data/lib/guacamole/aql_query.rb +6 -1
  11. data/lib/guacamole/collection.rb +34 -66
  12. data/lib/guacamole/configuration.rb +53 -25
  13. data/lib/guacamole/document_model_mapper.rb +149 -38
  14. data/lib/guacamole/edge.rb +74 -0
  15. data/lib/guacamole/edge_collection.rb +91 -0
  16. data/lib/guacamole/exceptions.rb +0 -5
  17. data/lib/guacamole/graph_query.rb +31 -0
  18. data/lib/guacamole/model.rb +4 -0
  19. data/lib/guacamole/proxies/proxy.rb +7 -3
  20. data/lib/guacamole/proxies/relation.rb +22 -0
  21. data/lib/guacamole/railtie.rb +1 -1
  22. data/lib/guacamole/transaction.rb +177 -0
  23. data/lib/guacamole/version.rb +1 -1
  24. data/shared/transaction.js +66 -0
  25. data/spec/acceptance/aql_spec.rb +32 -40
  26. data/spec/acceptance/relations_spec.rb +239 -0
  27. data/spec/acceptance/spec_helper.rb +2 -2
  28. data/spec/fabricators/author_fabricator.rb +2 -0
  29. data/spec/setup/arangodb.sh +2 -2
  30. data/spec/unit/collection_spec.rb +20 -97
  31. data/spec/unit/configuration_spec.rb +73 -50
  32. data/spec/unit/document_model_mapper_spec.rb +84 -77
  33. data/spec/unit/edge_collection_spec.rb +174 -0
  34. data/spec/unit/edge_spec.rb +57 -0
  35. data/spec/unit/proxies/relation_spec.rb +35 -0
  36. metadata +22 -14
  37. data/lib/guacamole/proxies/referenced_by.rb +0 -15
  38. data/lib/guacamole/proxies/references.rb +0 -15
  39. data/spec/acceptance/association_spec.rb +0 -40
  40. data/spec/unit/example_spec.rb +0 -8
@@ -10,49 +10,41 @@ describe 'BasicAQLSupport' do
10
10
  let(:earth_pony) { Fabricate(:pony, type: ['Earthpony'], name: 'Candy Mane') }
11
11
  let(:unicorn_pegasus_pony) { Fabricate(:pony, type: ['Pegasus', 'Unicorn']) }
12
12
 
13
- context 'with experimental AQL support enabled' do
14
- before do
15
- Guacamole.configuration.experimental_features = [:aql_support]
16
-
17
- [pegasus_pony, earth_pony, unicorn_pegasus_pony]
18
- end
19
-
20
- it 'should retrieve models by simple AQL queries' do
21
- pony_by_name = PoniesCollection.by_aql('FILTER pony.name == @name', name: 'Candy Mane').first
22
- expect(pony_by_name).to eq earth_pony
23
- end
24
-
25
- it 'should retrieve models by more complex AQL queries' do
26
- ponies_by_type = PoniesCollection.by_aql('FILTER POSITION(pony.type, @pony_type, false) == true',
27
- pony_type: 'Pegasus')
28
- expect(ponies_by_type).to include unicorn_pegasus_pony
29
- expect(ponies_by_type).to include pegasus_pony
30
- end
31
-
32
- it 'should allow a custom RETURN statement' do
33
- custom_color = 'fancy white pink with sparkles'
34
- pony_by_name = PoniesCollection.by_aql('FILTER pony.name == @name',
35
- { name: 'Candy Mane' },
36
- return_as: %Q{RETURN MERGE(pony, {"color": "#{custom_color}"})}).first
37
- expect(pony_by_name.color).to eq custom_color
38
- end
39
-
40
- it 'should allow to disable the mapping' do
41
- pony_hash = PoniesCollection.by_aql('FILTER pony.name == @name',
42
- { name: 'Candy Mane' },
43
- mapping: false ).first
44
- expect(pony_hash).to be_an(Ashikawa::Core::Document)
45
- end
13
+ before do
14
+ [pegasus_pony, earth_pony, unicorn_pegasus_pony]
46
15
  end
47
16
 
48
- context 'without experimental AQL support enabled' do
49
- before do
50
- Guacamole.configuration.experimental_features = []
51
- end
17
+ it 'should retrieve models by simple AQL queries' do
18
+ pony_by_name = PoniesCollection.by_aql('FILTER pony.name == @name', name: 'Candy Mane').first
19
+ expect(pony_by_name).to eq earth_pony
20
+ end
21
+
22
+ it 'should retrieve models by more complex AQL queries' do
23
+ ponies_by_type = PoniesCollection.by_aql('FILTER POSITION(pony.type, @pony_type, false) == true',
24
+ pony_type: 'Pegasus')
25
+ expect(ponies_by_type).to include unicorn_pegasus_pony
26
+ expect(ponies_by_type).to include pegasus_pony
27
+ end
52
28
 
53
- it 'should raise an error accessing the AQL feature' do
54
- expect { PoniesCollection.by_aql("random AQL fragment") }.to raise_error(Guacamole::AQLNotSupportedError)
55
- end
29
+ it 'should allow a custom RETURN statement' do
30
+ custom_color = 'fancy white pink with sparkles'
31
+ pony_by_name = PoniesCollection.by_aql('FILTER pony.name == @name',
32
+ { name: 'Candy Mane' },
33
+ return_as: %{RETURN MERGE(pony, {"color": "#{custom_color}"})}).first
34
+ expect(pony_by_name.color).to eq custom_color
56
35
  end
57
36
 
37
+ it 'should allow to disable the mapping' do
38
+ pony_hash = PoniesCollection.by_aql('FILTER pony.name == @name',
39
+ { name: 'Candy Mane' },
40
+ mapping: false).first
41
+ expect(pony_hash).to be_an(Ashikawa::Core::Document)
42
+ end
43
+
44
+ it 'should allow to provide a custom `FOR x IN y` part' do
45
+ pony_by_name = PoniesCollection.by_aql('FILTER p.name == @name',
46
+ { name: 'Candy Mane' },
47
+ for_in: 'FOR p IN ponies', return_as: 'RETURN p').first
48
+ expect(pony_by_name).to eq earth_pony
49
+ end
58
50
  end
@@ -0,0 +1,239 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'guacamole'
3
+ require 'acceptance/spec_helper'
4
+
5
+ require 'fabricators/book'
6
+ require 'fabricators/author'
7
+
8
+ class Authorship
9
+ include Guacamole::Edge
10
+
11
+ from :authors
12
+ to :books
13
+ end
14
+
15
+ class BooksCollection
16
+ include Guacamole::Collection
17
+
18
+ map do
19
+ attribute :author, via: Authorship, inverse: true
20
+ end
21
+ end
22
+
23
+ class AuthorsCollection
24
+ include Guacamole::Collection
25
+
26
+ map do
27
+ attribute :books, via: Authorship
28
+ end
29
+ end
30
+
31
+ describe 'Graph based relations' do
32
+
33
+ context 'having a start vertex and multiple target vertices' do
34
+ context 'all are new' do
35
+ let(:suzanne_collins) { Fabricate.build(:author, name: 'Suzanne Collins') }
36
+
37
+ let(:the_hunger_games) { Fabricate.build(:book, title: 'The Hunger Games') }
38
+ let(:catching_fire) { Fabricate.build(:book, title: 'Catching Fire') }
39
+ let(:mockingjay) { Fabricate.build(:book, title: 'Mockingjay') }
40
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
41
+
42
+ it 'should create the start, all targets and connect them' do
43
+ suzanne_collins.books = panem_trilogy
44
+ AuthorsCollection.save suzanne_collins
45
+
46
+ author = AuthorsCollection.by_key(suzanne_collins.key)
47
+
48
+ expect(author.books.map(&:title)).to match_array panem_trilogy.map(&:title)
49
+ end
50
+ end
51
+
52
+ context 'one target is new' do
53
+ let(:suzanne_collins) { Fabricate.build(:author, name: 'Suzanne Collins') }
54
+
55
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
56
+ let(:catching_fire) { Fabricate(:book, title: 'Catching Fire') }
57
+ let(:mockingjay) { Fabricate.build(:book, title: 'Mockingjay') }
58
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
59
+
60
+ it 'should create the start, the new target and connect both the new and existing ones' do
61
+ suzanne_collins.books = panem_trilogy
62
+ AuthorsCollection.save suzanne_collins
63
+
64
+ author = AuthorsCollection.by_key(suzanne_collins.key)
65
+
66
+ expect(author.books.map(&:title)).to match_array panem_trilogy.map(&:title)
67
+ end
68
+ end
69
+
70
+ context 'existing start gets another target' do
71
+ let(:suzanne_collins) { Fabricate(:author, name: 'Suzanne Collins') }
72
+
73
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
74
+ let(:catching_fire) { Fabricate(:book, title: 'Catching Fire') }
75
+ let(:mockingjay) { Fabricate.build(:book, title: 'Mockingjay') }
76
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
77
+
78
+ before do
79
+ suzanne_collins.books = [the_hunger_games, catching_fire]
80
+ AuthorsCollection.save suzanne_collins
81
+ end
82
+
83
+ it 'should save the new target and connect it to the start' do
84
+ suzanne_collins.books << mockingjay
85
+ AuthorsCollection.save suzanne_collins
86
+
87
+ author = AuthorsCollection.by_key(suzanne_collins.key)
88
+
89
+ expect(author.books.map(&:title)).to match_array panem_trilogy.map(&:title)
90
+ end
91
+ end
92
+
93
+ context 'new connection between existing start and existing target' do
94
+ let(:suzanne_collins) { Fabricate(:author, name: 'Suzanne Collins') }
95
+
96
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
97
+ let(:catching_fire) { Fabricate(:book, title: 'Catching Fire') }
98
+ let(:mockingjay) { Fabricate(:book, title: 'Mockingjay') }
99
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
100
+
101
+ before do
102
+ suzanne_collins.books = [the_hunger_games, catching_fire]
103
+ AuthorsCollection.save suzanne_collins
104
+ end
105
+
106
+ it 'should just add a connection between start and target' do
107
+ suzanne_collins.books << mockingjay
108
+ AuthorsCollection.save suzanne_collins
109
+
110
+ author = AuthorsCollection.by_key(suzanne_collins.key)
111
+
112
+ expect(author.books.map(&:title)).to match_array panem_trilogy.map(&:title)
113
+ end
114
+ end
115
+
116
+ context 'remove an existing connection' do
117
+ let(:suzanne_collins) { Fabricate(:author, name: 'Suzanne Collins') }
118
+
119
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
120
+ let(:catching_fire) { Fabricate(:book, title: 'Catching Fire') }
121
+ let(:mockingjay) { Fabricate(:book, title: 'Mockingjay') }
122
+ let(:deathly_hallows) { Fabricate(:book, title: 'Deathly Hallows') }
123
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
124
+
125
+ let(:authorships_count) { -> { AuthorshipsCollection.by_example(_from: suzanne_collins._id).count } }
126
+
127
+ before do
128
+ suzanne_collins.books = [the_hunger_games, catching_fire, mockingjay, deathly_hallows]
129
+ AuthorsCollection.save suzanne_collins
130
+ end
131
+
132
+ it 'should remove the edge' do
133
+ suzanne_collins.books.pop
134
+
135
+ expect { AuthorsCollection.save suzanne_collins }.to change(&authorships_count).by -1
136
+ end
137
+
138
+ it 'should not remove the target vertex' do
139
+ suzanne_collins.books.pop
140
+
141
+ AuthorsCollection.save suzanne_collins
142
+
143
+ expect(BooksCollection.by_key(deathly_hallows.key)).not_to be_nil
144
+ end
145
+
146
+ context 'removing the target' do
147
+ # This is just a sanity check at this point since it is handled by ArangoDB itself
148
+ it 'should remove the edge too' do
149
+ expect { BooksCollection.delete(deathly_hallows) }.to change(&authorships_count).by -1
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'remove one target should remove the edge too' do
155
+ let(:suzanne_collins) { Fabricate(:author, name: 'Suzanne Collins') }
156
+
157
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
158
+ let(:catching_fire) { Fabricate(:book, title: 'Catching Fire') }
159
+ let(:mockingjay) { Fabricate(:book, title: 'Mockingjay') }
160
+ let(:deathly_hallows) { Fabricate(:book, title: 'Deathly Hallows') }
161
+ let(:panem_trilogy) { [the_hunger_games, catching_fire, mockingjay] }
162
+
163
+ let(:authorships_count) { -> { AuthorshipsCollection.by_example(_from: suzanne_collins._id).count } }
164
+
165
+ before do
166
+ suzanne_collins.books = [the_hunger_games, catching_fire, mockingjay, deathly_hallows]
167
+ AuthorsCollection.save suzanne_collins
168
+ end
169
+ end
170
+ end
171
+
172
+ context 'having the target vertex and one target' do
173
+ context 'all are new' do
174
+ let(:suzanne_collins) { Fabricate.build(:author, name: 'Suzanne Collins') }
175
+
176
+ let(:the_hunger_games) { Fabricate.build(:book, title: 'The Hunger Games') }
177
+
178
+ it 'should create the target, the start and connects them' do
179
+ the_hunger_games.author = suzanne_collins
180
+ BooksCollection.save the_hunger_games
181
+
182
+ book = BooksCollection.by_key the_hunger_games.key
183
+
184
+ expect(book.author.name).to eq suzanne_collins.name
185
+ end
186
+ end
187
+
188
+ context 'the target is new' do
189
+ let(:suzanne_collins) { Fabricate.build(:author, name: 'Suzanne Collins') }
190
+
191
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
192
+
193
+ it 'should create the start and make the connection' do
194
+ the_hunger_games.author = suzanne_collins
195
+ BooksCollection.save the_hunger_games
196
+
197
+ book = BooksCollection.by_key the_hunger_games.key
198
+
199
+ expect(book.author.name).to eq suzanne_collins.name
200
+ end
201
+ end
202
+
203
+ context 'existing target gets another start' do
204
+ let(:jk_rowling) { Fabricate.build(:author, name: 'J.K. Rowling') }
205
+ let(:suzanne_collins) { Fabricate.build(:author, name: 'Suzanne Collins') }
206
+
207
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
208
+
209
+ before do
210
+ the_hunger_games.author = jk_rowling
211
+ BooksCollection.save the_hunger_games
212
+ end
213
+
214
+ it 'should create the new target and connect it with the start' do
215
+ the_hunger_games.author = suzanne_collins
216
+ BooksCollection.save the_hunger_games
217
+
218
+ book = BooksCollection.by_key the_hunger_games.key
219
+
220
+ expect(book.author.name).to eq suzanne_collins.name
221
+ end
222
+ end
223
+
224
+ context 'new connection between existing start and existing target' do
225
+ let(:suzanne_collins) { Fabricate(:author, name: 'Suzanne Collins') }
226
+
227
+ let(:the_hunger_games) { Fabricate(:book, title: 'The Hunger Games') }
228
+
229
+ it 'should just connect the target with the start' do
230
+ the_hunger_games.author = suzanne_collins
231
+ BooksCollection.save the_hunger_games
232
+
233
+ book = BooksCollection.by_key the_hunger_games.key
234
+
235
+ expect(book.author.name).to eq suzanne_collins.name
236
+ end
237
+ end
238
+ end
239
+ end
@@ -37,9 +37,9 @@ ENV['GUACAMOLE_ENV'] = 'test'
37
37
  Guacamole.configure do |config|
38
38
  logger = Logging.logger['guacamole_logger']
39
39
  logger.add_appenders(
40
- Logging.appenders.file('log/acceptance.log')
40
+ Logging.appenders.file(File.join(__dir__, '..', '..', 'log/acceptance.log'))
41
41
  )
42
- logger.level = :info
42
+ logger.level = :debug
43
43
 
44
44
  config.logger = logger
45
45
 
@@ -1,5 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ Fabricator(:author)
4
+
3
5
  Fabricator(:author_with_three_books, from: :author) do
4
6
  name 'Star Swirl the Bearded'
5
7
 
@@ -7,8 +7,8 @@ NAME=ArangoDB-$VERSION
7
7
 
8
8
  if [ ! -d "$DIR/$NAME" ]; then
9
9
  # download ArangoDB
10
- echo "wget http://www.arangodb.org/repositories/travisCI/$NAME.tar.gz"
11
- wget http://www.arangodb.org/repositories/travisCI/$NAME.tar.gz
10
+ echo "wget http://www.arangodb.com/repositories/travisCI/$NAME.tar.gz"
11
+ wget --no-check-certificate http://www.arangodb.com/repositories/travisCI/$NAME.tar.gz
12
12
  echo "tar zxf $NAME.tar.gz"
13
13
  tar zvxf $NAME.tar.gz
14
14
  mv `find . -type d -name "ArangoDB-*"` $NAME
@@ -69,16 +69,31 @@ describe Guacamole::Collection do
69
69
  end
70
70
  end
71
71
 
72
+ describe 'graph' do
73
+ before do
74
+ subject.graph = nil
75
+ end
76
+
77
+ it 'should default to Guacamole.configuration.graph' do
78
+ default_graph = double('Graph')
79
+ configuration = double('Configuration', graph: default_graph)
80
+ allow(Guacamole).to receive(:configuration).and_return(configuration)
81
+
82
+ expect(subject.graph).to eq default_graph
83
+ end
84
+ end
85
+
72
86
  describe 'connection' do
87
+ let(:graph) { double('Graph') }
88
+ let(:vertex_collection) { double('VertexCollection') }
89
+
73
90
  before do
74
91
  subject.connection = nil
92
+ allow(subject).to receive(:graph).and_return(graph)
75
93
  end
76
94
 
77
- it 'should default to the collection "collection_name" in the database' do
78
- database = double('Database')
79
- allow(subject).to receive(:database).and_return(database)
80
-
81
- expect(database).to receive(:[]).with(subject.collection_name)
95
+ it 'should be fetched through the #graph and default to "collection_name"' do
96
+ expect(graph).to receive(:add_vertex_collection).with(subject.collection_name)
82
97
 
83
98
  subject.connection
84
99
  end
@@ -125,11 +140,8 @@ describe Guacamole::Collection do
125
140
  end
126
141
 
127
142
  describe 'save' do
128
-
129
143
  before do
130
144
  allow(mapper).to receive(:model_to_document).with(model).and_return(document)
131
- allow(mapper).to receive(:referenced_by_models).and_return([])
132
- allow(mapper).to receive(:referenced_models).and_return([])
133
145
  end
134
146
 
135
147
  let(:key) { double('Key') }
@@ -139,7 +151,6 @@ describe Guacamole::Collection do
139
151
  let(:response) { double('Hash') }
140
152
 
141
153
  context 'a valid model' do
142
-
143
154
  before do
144
155
  allow(model).to receive(:valid?).and_return(true)
145
156
  allow(connection).to receive(:create_document).with(document).and_return(document)
@@ -171,7 +182,6 @@ describe Guacamole::Collection do
171
182
  end
172
183
 
173
184
  context 'which is persisted' do
174
-
175
185
  before do
176
186
  allow(model).to receive(:persisted?).and_return(true)
177
187
  allow(connection).to receive(:replace).and_return(response)
@@ -207,7 +217,6 @@ describe Guacamole::Collection do
207
217
  end
208
218
 
209
219
  context 'an invalid model' do
210
-
211
220
  before do
212
221
  expect(model).to receive(:valid?).and_return(false)
213
222
  end
@@ -266,12 +275,9 @@ describe Guacamole::Collection do
266
275
  end
267
276
 
268
277
  describe 'create' do
269
-
270
278
  before do
271
279
  allow(connection).to receive(:create_document).with(document).and_return(document)
272
280
  allow(mapper).to receive(:model_to_document).with(model).and_return(document)
273
- allow(mapper).to receive(:referenced_by_models).and_return([])
274
- allow(mapper).to receive(:referenced_models).and_return([])
275
281
  end
276
282
 
277
283
  let(:key) { double('Key') }
@@ -319,92 +325,9 @@ describe Guacamole::Collection do
319
325
 
320
326
  subject.create model
321
327
  end
322
-
323
- context 'with referenced model' do
324
- let(:referenced_model) { double('ReferencedModel') }
325
- let(:referenced_model_name) { :some_referenced_model }
326
- let(:referenced_models) { [referenced_model_name] }
327
- let(:collection_for_referenced_model) { double('Collection') }
328
-
329
- before do
330
- allow(referenced_model).to receive(:persisted?).and_return(false)
331
- allow(mapper).to receive(:collection_for)
332
- .with(referenced_model_name)
333
- .and_return(collection_for_referenced_model)
334
- allow(mapper).to receive(:referenced_models).and_return(referenced_models)
335
- allow(model).to receive(:send).with(referenced_model_name).and_return(referenced_model)
336
- allow(collection_for_referenced_model).to receive(:save).with(referenced_model)
337
- end
338
-
339
- it 'should save each referenced model' do
340
- expect(collection_for_referenced_model).to receive(:save).with(referenced_model)
341
-
342
- subject.create model
343
- end
344
-
345
- it 'should save the reference only if it is not already persisted' do
346
- allow(referenced_model).to receive(:persisted?).and_return(true)
347
- expect(collection_for_referenced_model).not_to receive(:save).with(referenced_model)
348
-
349
- subject.create model
350
- end
351
-
352
- it 'should save the reference only if it is present' do
353
- allow(model).to receive(:send).with(:some_referenced_model).and_return(nil)
354
- expect(collection_for_referenced_model).not_to receive(:save).with(referenced_model)
355
-
356
- subject.create model
357
- end
358
- end
359
-
360
- context 'with referenced_by model' do
361
- let(:referenced_by_model) { double('ReferencedByModel') }
362
- let(:referenced_by_model_name) { :some_referenced_by_model }
363
- let(:referenced_by_models) { [referenced_by_model_name] }
364
- let(:collection_for_referenced_by_model) { double('Collection') }
365
-
366
- before do
367
- allow(referenced_by_model).to receive(:persisted?).and_return(false)
368
- allow(referenced_by_model).to receive("#{model.class.name.demodulize.underscore}=").with(model)
369
- allow(mapper).to receive(:collection_for)
370
- .with(referenced_by_model_name)
371
- .and_return(collection_for_referenced_by_model)
372
- allow(mapper).to receive(:referenced_by_models).and_return(referenced_by_models)
373
- allow(model).to receive(:send).with(referenced_by_model_name).and_return([referenced_by_model])
374
- allow(collection_for_referenced_by_model).to receive(:save).with(referenced_by_model)
375
- end
376
-
377
- it 'should save the references' do
378
- expect(collection_for_referenced_by_model).to receive(:save).with(referenced_by_model)
379
-
380
- subject.create model
381
- end
382
-
383
- it 'should save the references only if it is not already persisted' do
384
- allow(referenced_by_model).to receive(:persisted?).and_return(true)
385
- expect(collection_for_referenced_by_model).not_to receive(:save).with(referenced_by_model)
386
-
387
- subject.create model
388
- end
389
-
390
- it 'should save the references only if it is present' do
391
- allow(model).to receive(:send).with(referenced_by_model_name).and_return([])
392
- expect(collection_for_referenced_by_model).not_to receive(:save).with(referenced_by_model)
393
-
394
- subject.create model
395
- end
396
-
397
- it 'should ensure the references have the parent model assigned prior saving' do
398
- expect(referenced_by_model).to receive("#{model.class.name.demodulize.underscore}=").with(model).ordered
399
- expect(collection_for_referenced_by_model).to receive(:save).with(referenced_by_model).ordered
400
-
401
- subject.create model
402
- end
403
- end
404
328
  end
405
329
 
406
330
  context 'an invalid model' do
407
-
408
331
  before do
409
332
  expect(model).to receive(:valid?).and_return(false)
410
333
  end