/
model_spec.rb
711 lines (581 loc) · 20 KB
/
model_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
# Specs written by Nick Sieger and modified by Andreas Ronge
describe Neo4j::Model do
describe "new" do
before :each do
@model = Neo4j::Model.new
end
subject { @model }
it { should_not be_persisted }
it "should allow access to properties before it is saved" do
@model["fur"] = "none"
@model["fur"].should == "none"
end
it "validation is performed when properties are changed" do
v = IceCream.new
v.should_not be_valid
v.flavour = 'vanilla'
v.should be_valid
end
it "validation is performed after save" do
v = IceCream.new(:flavour => 'vanilla')
v.save
v.should be_valid
end
it "validation is skipped if save(:validate => false)" do
v = IceCream.new(:name => 'illegal')
v.save(:validate => false).should be_true
v.should be_persisted
end
it "accepts a hash of properties which will be validated" do
v = IceCream.new(:flavour => 'vanilla')
v.should be_valid
end
it "save should create a new node" do
v = IceCream.new(:flavour => 'q')
v.save
Neo4j::Node.should exist(v)
end
it "has nil as id befored saved" do
v = IceCream.new(:flavour => 'andreas')
v.id.should == nil
end
end
describe "load" do
it "should load a previously stored node" do
model = Neo4j::Model.create
result = Neo4j::Model.load(model.id)
result.should == model
result.should be_persisted
end
end
describe "transaction" do
it "runs a block in a transaction" do
id = IceCream.transaction do
a = IceCream.create :flavour => 'vanilla'
a.ingredients << Neo4j::Node.new
a.id
end
IceCream.load(id).should_not be_nil
end
it "takes a 'tx' parameter that can be used to rollback the transaction" do
id = IceCream.transaction do |tx|
a = IceCream.create :flavour => 'vanilla'
a.ingredients << Neo4j::Node.new
tx.fail
a.id
end
IceCream.load(id).should be_nil
end
end
describe "save" do
it "stores a new model in the database" do
model = IceCream.new
model.flavour = "vanilla"
model.save
model.should be_persisted
IceCream.load(model.id).should == model
end
it "stores a created and modified model in the database" do
model = IceCream.new
model.flavour = "vanilla"
model.save
model.should be_persisted
IceCream.load(model.id).should == model
end
it "does not save the model if it is invalid" do
model = IceCream.new
model.save.should_not be_true
model.should_not be_valid
model.should_not be_persisted
model.id.should be_nil
end
it "new_record? is false before saved and true after saved (if saved was successful)" do
model = IceCream.new(:flavour => 'vanilla')
model.should be_new_record
model.save.should be_true
model.should_not be_new_record
end
it "does not modify the attributes if validation fails when run in a transaction" do
model = IceCream.create(:flavour => 'vanilla')
IceCream.transaction do
model.flavour = "horse"
model.should be_valid
model.save
model.flavour = nil
model.flavour.should be_nil
model.should_not be_valid
model.save
end
model.reload.flavour.should == 'vanilla'
end
it "does not modify relationships if validation fails when save is run in a transaction" do
model = IceCream.create(:flavour => 'vanilla')
model.ingredients << Ingredient.create(:name => 'sugar')
model.save
IceCream.transaction do
model.flavour = nil
model.ingredients << Ingredient.create(:name => 'flour')
model.save.should be_false
end
model.reload.flavour.should == 'vanilla'
model.ingredients.size.should == 1
model.ingredients.first.name.should == 'sugar'
end
it "create can initialize the object with a block" do
model = IceCream.create! {|o| o.flavour = 'vanilla'}
model.should be_persisted
model.flavour = 'vanilla'
model = IceCream.create {|o| o.flavour = 'vanilla'}
model.should be_persisted
model.flavour = 'vanilla'
end
end
describe "error" do
it "the validation method 'errors' returns the validation errors" do
p = IceCream.new
p.should_not be_valid
p.errors.keys[0].should == :flavour
p.flavour = 'vanilla'
p.should be_valid
p.errors.size.should == 0
end
end
describe "ActiveModel::Dirty" do
it "implements attribute_changed?, _change, _changed, _was, _changed? methods" do
p = IceCream.new
p.should_not be_changed
p.flavour = 'kalle'
p.should be_changed
p.flavour_changed?.should == true
p.flavour_change.should == [nil, 'kalle']
p.flavour_was.should == nil
p.flavour_changed?.should be_true
p.flavour_was.should == nil
p.flavour = 'andreas'
p.flavour_change.should == ['kalle', 'andreas']
p.save
p.should_not be_changed
end
end
describe "find" do
class ReferenceNode < Neo4j::Rails::Model
property :name
index :name
end
after(:each) do
Neo4j.threadlocal_ref_node = nil
end
it "should load all nodes of that type from the database" do
model = IceCream.create :flavour => 'vanilla'
IceCream.all.should include(model)
end
it "should allow switching the reference node" do
reference = ReferenceNode.create(:name => 'Name')
Neo4j.threadlocal_ref_node = reference
icecream = IceCream.create(:flavour => 'vanilla')
IceCream.first.should == icecream
end
it "switching the reference node should change the scope of finder queries" do
reference1 = ReferenceNode.create(:name => 'Ref1')
reference2 = ReferenceNode.create(:name => 'Ref2')
Neo4j.threadlocal_ref_node = reference1
icecream_for_reference1 = IceCream.create(:flavour => 'vanilla')
IceCream.all.size.should == 1
IceCream.first.should == icecream_for_reference1
Neo4j.threadlocal_ref_node = reference2
icecream_for_reference2 = IceCream.create(:flavour => 'strawberry')
IceCream.all.size.should == 1
IceCream.first.should == icecream_for_reference2
end
it "switching the reference node works for multiple entities" do
reference1 = ReferenceNode.create(:name => 'Ref1')
reference2 = ReferenceNode.create(:name => 'Ref2')
Neo4j.threadlocal_ref_node = reference1
icecream_for_reference1 = IceCream.create(:flavour => 'vanilla')
ingredient_for_reference_1 = Ingredient.create(:name => 'sugar')
IceCream.all.size.should == 1
IceCream.first.should == icecream_for_reference1
Ingredient.all.size.should == 1
Ingredient.first.should == ingredient_for_reference_1
Neo4j.threadlocal_ref_node = reference2
icecream_for_reference2 = IceCream.create(:flavour => 'strawberry')
ingredient_for_reference_2 = Ingredient.create(:name => 'eggs')
IceCream.all.size.should == 1
IceCream.first.should == icecream_for_reference2
Ingredient.all.size.should == 1
Ingredient.first.should == ingredient_for_reference_2
end
it "should find the node given it's id" do
model = IceCream.create(:flavour => 'thing')
IceCream.find(model.neo_id.to_s).should == model
end
it "should find a model by one of its attributes" do
model = IceCream.create(:flavour => 'vanilla')
IceCream.find("flavour: vanilla").should == model
end
it "should only find two by same attribute" do
m1 = IceCream.create(:flavour => 'vanilla')
m2 = IceCream.create(:flavour => 'vanilla')
m3 = IceCream.create(:flavour => 'fish')
IceCream.all("flavour: vanilla").size.should == 2
end
context "when node is attached to default ref node" do
let(:reference1) { ReferenceNode.create(:name => 'Ref1') }
let(:reference2) { ReferenceNode.create(:name => 'Ref2') }
class IndexedGlobalModel < Neo4j::Model
property :name
index :name
ref_node { Neo4j.default_ref_node }
end
context "given node is created with threadlocal node set" do
before(:each) do
Neo4j.threadlocal_ref_node = reference1
@model = IndexedGlobalModel.create!(:name => 'foo')
end
it "should find the model when threadlocal node is set" do
Neo4j.threadlocal_ref_node = reference2
IndexedGlobalModel.find(:name => 'foo').should == @model
end
it "should find the node when threadlocal node is not set" do
Neo4j.threadlocal_ref_node = nil
IndexedGlobalModel.find(:name => 'foo').should == @model
end
end
context "given node is created with threadlocal node set" do
before(:each) do
Neo4j.threadlocal_ref_node = nil
@model = IndexedGlobalModel.create!(:name => 'foo')
end
it "should find the node when threadlocal node is not set" do
Neo4j.threadlocal_ref_node = nil
IndexedGlobalModel.find(:name => 'foo').should == @model
end
it "should find the model when threadlocal node is set" do
Neo4j.threadlocal_ref_node = reference1
IndexedGlobalModel.find(:name => 'foo').should == @model
end
end
end
end
describe "destroy" do
before :each do
@model = Neo4j::Model.create
end
it "should remove the model from the database" do
id = @model.neo_id
@model.destroy
Neo4j::Node.load(id).should be_nil
end
end
describe "create" do
it "should save the model and return it" do
model = Neo4j::Model.create
model.should be_persisted
end
it "should accept attributes to be set" do
model = Neo4j::Model.create :name => "Nick"
model[:name].should == "Nick"
end
it "bang version should raise an exception if save returns false" do
expect { IceCream.create! }.to raise_error(Neo4j::Model::RecordInvalidError)
end
it "bang version should NOT raise an exception" do
icecream = IceCream.create! :flavour => 'vanilla'
icecream.flavour.should == 'vanilla'
end
it "should run before and after create callbacks" do
class RunBeforeAndAfterCreateCallbackModel < Neo4j::Rails::Model
property :created
before_create :timestamp
def timestamp
self.created = "yes"
fail "Expected new record" unless new_record?
end
after_create :mark_saved
attr_reader :saved
def mark_saved
@saved = true
end
end
model = RunBeforeAndAfterCreateCallbackModel.create!
model.created.should_not be_nil
model.saved.should_not be_nil
end
it "should run before and after save callbacks" do
class RunBeforeAndAfterSaveCallbackModel < Neo4j::Rails::Model
property :created
before_save :timestamp
def timestamp
self.created = "yes"
fail "Expected new record" unless new_record?
end
after_save :mark_saved
attr_reader :saved
def mark_saved
@saved = true
end
end
model = RunBeforeAndAfterSaveCallbackModel.create!
model.created.should_not be_nil
model.saved.should_not be_nil
end
it "should run before and after new & save callbacks" do
class RunBeforeAndAfterNewAndSaveCallbackModel < Neo4j::Rails::Model
property :created
before_save :timestamp
def timestamp
self.created = "yes"
fail "Expected new record" unless new_record?
end
after_save :mark_saved
attr_reader :saved
def mark_saved
@saved = true
end
end
model = RunBeforeAndAfterNewAndSaveCallbackModel.new
model.save
model.created.should_not be_nil
model.saved.should_not be_nil
end
end
describe "update_attributes" do
it "should save the attributes" do
model = Neo4j::Model.new
model.update_attributes(:a => 1, :b => 2).should be_true
model[:a].should == 1
model[:b].should == 2
end
it "should not update the model if it is invalid" do
class UpdatedAttributesModel < Neo4j::Rails::Model
property :name
validates_presence_of :name
end
model = UpdatedAttributesModel.create!(:name => "vanilla")
model.update_attributes(:name => nil).should be_false
model.reload.name.should == "vanilla"
end
end
describe "properties" do
it "not required to run in a transaction (will create one)" do
cream = IceCream.create :flavour => 'x'
cream.flavour = 'vanilla'
cream.flavour.should == 'vanilla'
cream.should exist
end
it "should reuse the same transaction - not create a new one if one is already available" do
cream = nil
IceCream.transaction do
cream = IceCream.create :flavour => 'x'
cream.flavour = 'vanilla'
cream.should exist
# rollback the transaction
Neo4j::Rails::Transaction.fail
end
cream.should_not exist
end
it "should roll back a transaction when the transaction fails within a nested transaction" do
cream = nil
IceCream.transaction do
cream = IceCream.create :flavour => 'x'
cream.flavour = 'vanilla'
cream.should exist
Ingredient.transaction do
ingredient = Ingredient.create(:name => 'sugar')
cream.ingredients << ingredient
# rollback the transaction
Neo4j::Rails::Transaction.fail
end
end
cream.should_not exist
end
it "should commit a two level nested transaction" do
cream = nil
IceCream.transaction do
cream = IceCream.create :flavour => 'x'
cream.flavour = 'vanilla'
cream.should exist
Ingredient.transaction do
ingredient = Ingredient.create(:name => 'sugar')
cream.ingredients << ingredient
end
end
cream.should exist
cream.flavour.should == 'vanilla'
cream.ingredients.size.should == 1
cream.ingredients.first.name.should == 'sugar'
end
end
describe "Neo4j::Rails::Validations::UniquenessValidator" do
before(:all) do
class ValidThing < Neo4j::Model
index :email
validates :email, :uniqueness => true
end
@klass = ValidThing
end
it "have correct kind" do
Neo4j::Rails::Validations::UniquenessValidator.kind.should == :uniqueness
end
it "should not allow to create two nodes with unique fields" do
a = @klass.create(:email => 'abc@se.com')
b = @klass.new(:email => 'abc@se.com')
b.save.should be_false
b.errors.size.should == 1
end
it "should allow to create two nodes with not unique fields" do
@klass.create(:email => 'abc@gmail.copm')
b = @klass.new(:email => 'ab@gmail.com')
b.save.should_not be_false
b.errors.size.should == 0
end
end
describe "attr_accessible" do
before(:all) do
@klass = create_model do
attr_accessor :name, :credit_rating
attr_protected :credit_rating
end
end
it "given attributes are sanitized before assignment in method: attributes" do
customer = @klass.new
customer.attributes = {"name" => "David", "credit_rating" => "Excellent"}
customer.name.should == 'David'
customer.credit_rating.should be_nil
customer.credit_rating= "Average"
customer.credit_rating.should == 'Average'
end
it "given attributes are sanitized before assignment in method: new" do
customer = @klass.new("name" => "David", "credit_rating" => "Excellent")
customer.name.should == 'David'
customer.credit_rating.should be_nil
customer.credit_rating= "Average"
customer.credit_rating.should == 'Average'
end
it "given attributes are sanitized before assignment in method: create" do
customer = @klass.create("name" => "David", "credit_rating" => "Excellent")
customer.name.should == 'David'
customer.credit_rating.should be_nil
customer.credit_rating= "Average"
customer.credit_rating.should == 'Average'
end
it "given attributes are sanitized before assignment in method: update_attributes" do
customer = @klass.new
customer.update_attributes("name" => "David", "credit_rating" => "Excellent")
customer.name.should == 'David'
customer.credit_rating.should be_nil
customer.credit_rating= "Average"
customer.credit_rating.should == 'Average'
end
end
describe "has_one, has_n, incoming" do
before(:all) do
item = create_model do
property :name
validates :name, :presence => true
def to_s
"Item #{name} class: #{self.class} id: #{self.object_id}"
end
end
@order = create_model do
property :name
has_n(:items).to(item)
validates :name, :presence => true
def to_s
"Order #{name} class: #{self.class} id: #{self.object_id}"
end
end
@item = item # used as closure
@item.has_n(:orders).from(@order, :items)
end
it "add nodes without save should only store it in memory" do
order = @order.new :name => 'order'
item = @item.new :name => 'item'
# then
item.orders << order
item.orders.should include(order)
Neo4j.all_nodes.should_not include(item)
Neo4j.all_nodes.should_not include(order)
end
it "add nodes with save should store it in db" do
order = @order.new :name => 'order'
item = @item.new :name => 'item'
# then
item.orders << order
item.orders.should include(order)
item.save
Neo4j.all_nodes.should include(item)
Neo4j.all_nodes.should include(order)
item.reload
item.orders.should include(order)
end
end
describe "i18n_scope" do
subject { Neo4j::Rails::Model.i18n_scope }
it { should == :neo4j }
end
describe "reachable_from_ref_node?" do
let(:ref_1) { Neo4j::Rails::Model.create!(:name => "Ref1") }
let(:ref_2) { Neo4j::Rails::Model.create!(:name => "Ref2") }
context "when node is not attached to default ref node" do
before(:each) do
Neo4j.threadlocal_ref_node = ref_1
@node_from_ref_1 = IceCream.create!(:flavour => 'Vanilla')
end
after(:each) do
Neo4j.threadlocal_ref_node = nil
end
context "when node is created under current threadlocal ref_node" do
it "should be true" do
Neo4j.threadlocal_ref_node = ref_1
@node_from_ref_1.should be_reachable_from_ref_node
end
end
context "when node is not created under current threadlocal ref_node" do
it "should be false" do
Neo4j.threadlocal_ref_node = ref_2
@node_from_ref_1.should_not be_reachable_from_ref_node
end
end
end
context "when node is attached to default ref node" do
let(:clazz) do
create_model do
ref_node { Neo4j.default_ref_node }
end
end
context "when threadlocal node is set" do
it "should be true" do
Neo4j.threadlocal_ref_node = ref_1
node = clazz.create!
node.should be_reachable_from_ref_node
end
end
context "when threadlocal node is not set" do
it "should be true" do
Neo4j.threadlocal_ref_node = nil
node = clazz.create!
node.should be_reachable_from_ref_node
end
end
end
end
describe "#columns" do
context "a model with no defined properties" do
it "should return an empty array" do
create_model.columns.should be_empty
end
end
context "a model with two defined property" do
it "should return an empty array" do
columns = create_model do
property :foo
property :bar
end.columns
columns.size.should == 2
columns.should include(:foo, :bar)
end
end
end
end