guacamole 0.3.0 → 0.4.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 (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