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.
- checksums.yaml +4 -4
- data/.hound.yml +1 -1
- data/.travis.yml +16 -15
- data/CHANGELOG.md +16 -0
- data/GOALS.md +8 -0
- data/Guardfile +4 -0
- data/README.md +21 -81
- data/guacamole.gemspec +3 -3
- data/lib/guacamole.rb +1 -0
- data/lib/guacamole/aql_query.rb +6 -1
- data/lib/guacamole/collection.rb +34 -66
- data/lib/guacamole/configuration.rb +53 -25
- data/lib/guacamole/document_model_mapper.rb +149 -38
- data/lib/guacamole/edge.rb +74 -0
- data/lib/guacamole/edge_collection.rb +91 -0
- data/lib/guacamole/exceptions.rb +0 -5
- data/lib/guacamole/graph_query.rb +31 -0
- data/lib/guacamole/model.rb +4 -0
- data/lib/guacamole/proxies/proxy.rb +7 -3
- data/lib/guacamole/proxies/relation.rb +22 -0
- data/lib/guacamole/railtie.rb +1 -1
- data/lib/guacamole/transaction.rb +177 -0
- data/lib/guacamole/version.rb +1 -1
- data/shared/transaction.js +66 -0
- data/spec/acceptance/aql_spec.rb +32 -40
- data/spec/acceptance/relations_spec.rb +239 -0
- data/spec/acceptance/spec_helper.rb +2 -2
- data/spec/fabricators/author_fabricator.rb +2 -0
- data/spec/setup/arangodb.sh +2 -2
- data/spec/unit/collection_spec.rb +20 -97
- data/spec/unit/configuration_spec.rb +73 -50
- data/spec/unit/document_model_mapper_spec.rb +84 -77
- data/spec/unit/edge_collection_spec.rb +174 -0
- data/spec/unit/edge_spec.rb +57 -0
- data/spec/unit/proxies/relation_spec.rb +35 -0
- metadata +22 -14
- data/lib/guacamole/proxies/referenced_by.rb +0 -15
- data/lib/guacamole/proxies/references.rb +0 -15
- data/spec/acceptance/association_spec.rb +0 -40
- data/spec/unit/example_spec.rb +0 -8
data/spec/acceptance/aql_spec.rb
CHANGED
@@ -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
|
-
|
14
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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 = :
|
42
|
+
logger.level = :debug
|
43
43
|
|
44
44
|
config.logger = logger
|
45
45
|
|
data/spec/setup/arangodb.sh
CHANGED
@@ -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.
|
11
|
-
wget http://www.arangodb.
|
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
|
78
|
-
|
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
|