mongoose 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +51 -1
- data/changes.txt +11 -0
- data/example/simple_examples.rb +47 -43
- data/lib/mongoose.rb +1 -3
- data/lib/mongoose/column.rb +10 -38
- data/lib/mongoose/database.rb +4 -13
- data/lib/mongoose/skiplist.rb +407 -336
- data/lib/mongoose/table.rb +53 -43
- data/lib/mongoose/util.rb +1 -1
- data/test/tc_relations.rb +139 -0
- data/test/tc_table.rb +51 -5
- data/test/ts_mongoose.rb +1 -0
- metadata +3 -2
data/lib/mongoose/table.rb
CHANGED
@@ -67,7 +67,7 @@ class Table
|
|
67
67
|
#-----------------------------------------------------------------------------
|
68
68
|
def self.has_many(kind)
|
69
69
|
table_name = Util.singularize(kind.to_s).to_sym
|
70
|
-
class_name = Util.us_case_to_class_case(table_name
|
70
|
+
class_name = Util.us_case_to_class_case(table_name)
|
71
71
|
col = Util.col_name_for_class(self.to_s)
|
72
72
|
|
73
73
|
define_method(kind.to_sym) do
|
@@ -82,7 +82,7 @@ class Table
|
|
82
82
|
#-----------------------------------------------------------------------------
|
83
83
|
def self.has_one(kind)
|
84
84
|
table_name = kind.to_sym
|
85
|
-
class_name = Util.us_case_to_class_case(table_name
|
85
|
+
class_name = Util.us_case_to_class_case(table_name)
|
86
86
|
col = Util.col_name_for_class(self.to_s)
|
87
87
|
|
88
88
|
define_method(kind.to_sym) do
|
@@ -95,7 +95,8 @@ class Table
|
|
95
95
|
# Table.belongs_to
|
96
96
|
#-----------------------------------------------------------------------------
|
97
97
|
def self.belongs_to(kind)
|
98
|
-
|
98
|
+
table_name = kind.to_sym
|
99
|
+
class_name = Util.us_case_to_class_case(table_name)
|
99
100
|
|
100
101
|
define_method(kind) do
|
101
102
|
klass = Object.const_get(class_name.to_s)
|
@@ -113,28 +114,14 @@ class Table
|
|
113
114
|
# Table.last_id_used
|
114
115
|
#-----------------------------------------------------------------------------
|
115
116
|
def self.last_id_used
|
116
|
-
self.
|
117
|
-
end
|
118
|
-
|
119
|
-
#-----------------------------------------------------------------------------
|
120
|
-
# Table.last_id_used=
|
121
|
-
#-----------------------------------------------------------------------------
|
122
|
-
def self.last_id_used=(value)
|
123
|
-
self.db.tables[self][:last_id_used] = value
|
117
|
+
self.class.read_header[:last_id_used]
|
124
118
|
end
|
125
119
|
|
126
120
|
#-----------------------------------------------------------------------------
|
127
121
|
# Table.deleted_recs_counter
|
128
122
|
#-----------------------------------------------------------------------------
|
129
123
|
def self.deleted_recs_counter
|
130
|
-
self.
|
131
|
-
end
|
132
|
-
|
133
|
-
#-----------------------------------------------------------------------------
|
134
|
-
# Table.deleted_recs_counter=
|
135
|
-
#-----------------------------------------------------------------------------
|
136
|
-
def self.deleted_recs_counter=(value)
|
137
|
-
self.db.tables[self][:deleted_recs_counter]=(value)
|
124
|
+
self.class.read_header[:deleted_recs_counter]
|
138
125
|
end
|
139
126
|
|
140
127
|
#-----------------------------------------------------------------------------
|
@@ -149,21 +136,16 @@ class Table
|
|
149
136
|
# Table.write_header
|
150
137
|
#-----------------------------------------------------------------------------
|
151
138
|
def self.write_header(header)
|
152
|
-
File.open(File.join(self.path, self.table_name.to_s + TBL_HDR_EXT), 'w'
|
153
|
-
|
154
|
-
end
|
139
|
+
File.open(File.join(self.path, self.table_name.to_s + TBL_HDR_EXT), 'w'
|
140
|
+
) { |f| YAML.dump(header, f) }
|
155
141
|
end
|
156
142
|
|
157
143
|
#-----------------------------------------------------------------------------
|
158
144
|
# Table.init_table
|
159
145
|
#-----------------------------------------------------------------------------
|
160
146
|
def self.init_table
|
161
|
-
|
162
147
|
tbl_header = self.read_header
|
163
148
|
|
164
|
-
self.last_id_used = tbl_header[:last_id_used]
|
165
|
-
self.deleted_recs_counter = tbl_header[:deleted_recs_counter]
|
166
|
-
|
167
149
|
self.read_header[:columns].each do |c|
|
168
150
|
self.init_column(c[:name], c[:data_type], Object.full_const_get(c[:class])
|
169
151
|
)
|
@@ -385,9 +367,7 @@ class Table
|
|
385
367
|
# initialize
|
386
368
|
#-----------------------------------------------------------------------------
|
387
369
|
def initialize(*values)
|
388
|
-
self.class.columns.zip(values).each
|
389
|
-
send("#{c.name}=", v)
|
390
|
-
end
|
370
|
+
self.class.columns.zip(values).each { |c,v| send("#{c.name}=", v) }
|
391
371
|
end
|
392
372
|
|
393
373
|
#-----------------------------------------------------------------------------
|
@@ -395,25 +375,23 @@ class Table
|
|
395
375
|
#-----------------------------------------------------------------------------
|
396
376
|
def save
|
397
377
|
self.class.columns.each do |c|
|
378
|
+
# First checks to see if validates_presence_of was set in class def.
|
398
379
|
raise "Value required for #{c.name}!" if respond_to?('required?') and \
|
399
380
|
required?(c.name) and send(c.name).nil?
|
381
|
+
# Next checks to see if validates_presence_of was set in #add_column.
|
400
382
|
raise "Value required for #{c.name}!" if c.required? and send(c.name).nil?
|
401
383
|
end
|
402
384
|
|
385
|
+
# Add new record.
|
403
386
|
if @id.nil?
|
404
|
-
|
405
|
-
self.class.
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
@id = self.class.last_id_used
|
411
|
-
tbl_header[:last_id_used] = self.class.last_id_used
|
412
|
-
self.class.write_header(tbl_header)
|
387
|
+
id = increment_last_id_used
|
388
|
+
append_record(id, self.class.column_names[1..-1].collect { |col_name|
|
389
|
+
send(col_name) })
|
390
|
+
@id = id
|
391
|
+
# Update existing record.
|
413
392
|
else
|
414
393
|
update_record(@id, self.class.columns[1..-1].collect { |c| send(c.name) })
|
415
394
|
end
|
416
|
-
|
417
395
|
return true
|
418
396
|
end
|
419
397
|
|
@@ -431,12 +409,16 @@ class Table
|
|
431
409
|
raise IndexCorruptError, "Index ID does not match table ID!", caller \
|
432
410
|
unless rec[1] == @id
|
433
411
|
|
412
|
+
# First array position of record is the deleted flag: true means deleted
|
434
413
|
rec[0] = true
|
435
414
|
|
415
|
+
# Record is not actually deleted; it just has its deleted flag set to
|
416
|
+
# true.
|
436
417
|
write_record(fptr, fpos_rec_start, Marshal.dump(rec))
|
437
418
|
increment_deleted_recs_counter
|
438
419
|
end
|
439
420
|
|
421
|
+
# Remove all index recs pointing to this record.
|
440
422
|
self.class.columns.each_with_index do |c,i|
|
441
423
|
if i == 0
|
442
424
|
c.remove_index_rec(@id)
|
@@ -445,6 +427,7 @@ class Table
|
|
445
427
|
end
|
446
428
|
end
|
447
429
|
|
430
|
+
# Don't allow any more changes to this record.
|
448
431
|
freeze
|
449
432
|
end
|
450
433
|
|
@@ -463,9 +446,12 @@ class Table
|
|
463
446
|
fptr.seek(0, IO::SEEK_END)
|
464
447
|
fpos = fptr.tell
|
465
448
|
|
449
|
+
# Append record to end of file, making sure to add deleted flag and record
|
450
|
+
# id to front of record.
|
466
451
|
fpos = write_record(fptr, 'end', Marshal.dump([false, id].concat(values)))
|
467
452
|
end
|
468
453
|
|
454
|
+
# Update indexes with new record.
|
469
455
|
self.class.columns.each_with_index do |c,i|
|
470
456
|
if i == 0
|
471
457
|
c.add_index_rec(id, fpos)
|
@@ -495,15 +481,25 @@ class Table
|
|
495
481
|
|
496
482
|
old_rec_length = fptr.tell - fpos_rec_start
|
497
483
|
|
484
|
+
# If updates did not change record length, we can write it back out to
|
485
|
+
# the same spot in the file...
|
498
486
|
if new_rec.length == old_rec_length
|
499
487
|
write_record(fptr, fpos_rec_start, new_rec)
|
500
488
|
else
|
501
|
-
|
489
|
+
# Set deleted flag to true and update old rec position. Increment
|
490
|
+
# deleted records counter.
|
491
|
+
old_rec[0] = true
|
492
|
+
write_record(fptr, fpos_rec_start, Marshal.dump(old_rec))
|
502
493
|
increment_deleted_recs_counter
|
494
|
+
|
495
|
+
# Append the updated record to the end of the file and update the
|
496
|
+
# record id index.
|
497
|
+
fpos = write_record(fptr, 'end', new_rec)
|
503
498
|
self.class.columns[0].add_index_rec(id, fpos)
|
504
499
|
end
|
505
500
|
end
|
506
501
|
|
502
|
+
# Update all of the indexed columns with the updated record data.
|
507
503
|
self.class.columns[1..-1].each do |c|
|
508
504
|
unless temp_instance.send(c.name) == send(c.name)
|
509
505
|
if c.indexed?
|
@@ -514,14 +510,24 @@ class Table
|
|
514
510
|
end
|
515
511
|
end
|
516
512
|
|
513
|
+
#-----------------------------------------------------------------------------
|
514
|
+
# increment_last_id_used
|
515
|
+
#-----------------------------------------------------------------------------
|
516
|
+
def increment_last_id_used
|
517
|
+
tbl_header = self.class.read_header
|
518
|
+
tbl_header[:last_id_used] += 1
|
519
|
+
self.class.write_header(tbl_header)
|
520
|
+
return tbl_header[:last_id_used]
|
521
|
+
end
|
522
|
+
|
517
523
|
#-----------------------------------------------------------------------------
|
518
524
|
# increment_deleted_recs_counter
|
519
525
|
#-----------------------------------------------------------------------------
|
520
526
|
def increment_deleted_recs_counter
|
521
527
|
tbl_header = self.class.read_header
|
522
|
-
|
523
|
-
tbl_header[:deleted_recs_counter] = self.class.deleted_recs_counter
|
528
|
+
tbl_header[:deleted_recs_counter] += 1
|
524
529
|
self.class.write_header(tbl_header)
|
530
|
+
return tbl_header[:deleted_recs_counter]
|
525
531
|
end
|
526
532
|
|
527
533
|
#-----------------------------------------------------------------------------
|
@@ -551,7 +557,7 @@ class Collection
|
|
551
557
|
end
|
552
558
|
|
553
559
|
def each
|
554
|
-
records.each { |rec| yield rec }
|
560
|
+
@records.each { |rec| yield rec }
|
555
561
|
end
|
556
562
|
|
557
563
|
def <<(rec)
|
@@ -563,6 +569,10 @@ class Collection
|
|
563
569
|
def append(rec)
|
564
570
|
self << rec
|
565
571
|
end
|
572
|
+
|
573
|
+
def size
|
574
|
+
@records.size
|
575
|
+
end
|
566
576
|
end
|
567
577
|
|
568
578
|
end
|
data/lib/mongoose/util.rb
CHANGED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubygems'
|
6
|
+
require_gem 'Mongoose'
|
7
|
+
rescue LoadError
|
8
|
+
require 'mongoose'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Pilot < Mongoose::Table
|
12
|
+
has_one :plane
|
13
|
+
has_many :flights
|
14
|
+
end
|
15
|
+
|
16
|
+
class Plane < Mongoose::Table
|
17
|
+
belongs_to :pilot
|
18
|
+
end
|
19
|
+
|
20
|
+
class Flight < Mongoose::Table
|
21
|
+
belongs_to :pilot
|
22
|
+
end
|
23
|
+
|
24
|
+
class TestRelations < Test::Unit::TestCase
|
25
|
+
def setup
|
26
|
+
@db = Mongoose::Database.new(:path => Dir.tmpdir)
|
27
|
+
|
28
|
+
@db.create_table(:pilot) do |tbl|
|
29
|
+
tbl.add_indexed_column(:pilot_name, :string)
|
30
|
+
tbl.add_column(:years_flying, :integer)
|
31
|
+
end
|
32
|
+
|
33
|
+
@db.create_table(:plane) do |tbl|
|
34
|
+
tbl.add_indexed_column(:plane_name, :string)
|
35
|
+
tbl.add_column(:country, :string)
|
36
|
+
tbl.add_indexed_column(:speed, :integer)
|
37
|
+
tbl.add_column(:range, :integer)
|
38
|
+
tbl.add_column(:pilot_id, :integer)
|
39
|
+
end
|
40
|
+
|
41
|
+
@db.create_table(:flight) do |tbl|
|
42
|
+
tbl.add_column(:origin, :string)
|
43
|
+
tbl.add_column(:destination, :string)
|
44
|
+
tbl.add_column(:pilot_id, :integer)
|
45
|
+
end
|
46
|
+
|
47
|
+
@doolittle = Pilot.new
|
48
|
+
@doolittle.pilot_name = 'Doolittle, James'
|
49
|
+
@doolittle.years_flying = 15
|
50
|
+
@doolittle.save
|
51
|
+
|
52
|
+
@amelia = Pilot.new
|
53
|
+
@amelia.pilot_name = 'Earhart, Amelia'
|
54
|
+
@amelia.years_flying = 10
|
55
|
+
@amelia.save
|
56
|
+
|
57
|
+
rec = Plane.new
|
58
|
+
rec.plane_name = 'P-51'
|
59
|
+
rec.country = 'USA'
|
60
|
+
rec.speed = 402
|
61
|
+
rec.range = 1205
|
62
|
+
rec.pilot_id = @doolittle.id
|
63
|
+
rec.save
|
64
|
+
|
65
|
+
rec = Plane.new
|
66
|
+
rec.plane_name = 'Spitfire'
|
67
|
+
rec.country = 'Great Britain'
|
68
|
+
rec.speed = 333
|
69
|
+
rec.range = 454
|
70
|
+
rec.pilot_id = @amelia.id
|
71
|
+
rec.save
|
72
|
+
|
73
|
+
rec = Flight.new
|
74
|
+
rec.pilot_id = @doolittle.id
|
75
|
+
rec.origin = 'Army'
|
76
|
+
rec.destination = 'Nowhere'
|
77
|
+
rec.save
|
78
|
+
|
79
|
+
rec = Flight.new
|
80
|
+
rec.pilot_id = @amelia.id
|
81
|
+
rec.origin = 'USA'
|
82
|
+
rec.destination = 'France'
|
83
|
+
|
84
|
+
rec.save
|
85
|
+
rec = Flight.new
|
86
|
+
rec.pilot_id = @amelia.id
|
87
|
+
rec.origin = 'USA'
|
88
|
+
rec.destination = 'Phillipines'
|
89
|
+
rec.save
|
90
|
+
|
91
|
+
rec = Flight.new
|
92
|
+
rec.pilot_id = @amelia.id
|
93
|
+
rec.origin = 'China'
|
94
|
+
rec.destination = 'Unknown'
|
95
|
+
rec.save
|
96
|
+
end
|
97
|
+
|
98
|
+
def teardown
|
99
|
+
@db.drop_table(:pilot)
|
100
|
+
@db.drop_table(:plane)
|
101
|
+
@db.drop_table(:flight)
|
102
|
+
@db.close
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_has_one
|
106
|
+
assert_equal('P-51', @doolittle.plane.plane_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_has_many_001
|
110
|
+
assert_equal(3, @amelia.flights.size)
|
111
|
+
assert(@amelia.flights.any? {|f| f.destination == 'France'})
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_has_many_002
|
115
|
+
rec = Flight.new
|
116
|
+
rec.pilot_id = @amelia.id
|
117
|
+
rec.origin = 'Tuscon'
|
118
|
+
rec.destination = 'Phoenix'
|
119
|
+
@amelia.flights << rec
|
120
|
+
|
121
|
+
assert(@amelia.flights.any? {|f| f.origin == 'Tuscon'})
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_belongs_to_001
|
125
|
+
assert_equal('Earhart, Amelia', Plane.find(2).pilot.pilot_name)
|
126
|
+
assert_equal('Doolittle, James', Flight.find(1).pilot.pilot_name)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_belongs_to_002
|
130
|
+
red_baron = Pilot.new
|
131
|
+
red_baron.pilot_name = 'Baron, The Red'
|
132
|
+
red_baron.years_flying = 50
|
133
|
+
spitfire = Plane.find(:first) {|plane| plane.plane_name == 'Spitfire'}
|
134
|
+
spitfire.pilot = red_baron
|
135
|
+
|
136
|
+
assert('Baron, The Red', spitfire.pilot.pilot_name)
|
137
|
+
assert_equal('Spitfire', red_baron.plane.plane_name)
|
138
|
+
end
|
139
|
+
end
|
data/test/tc_table.rb
CHANGED
@@ -74,7 +74,17 @@ class TestTable < Test::Unit::TestCase
|
|
74
74
|
assert_equal(1205, rec.range)
|
75
75
|
end
|
76
76
|
|
77
|
-
def
|
77
|
+
def test_update_record_001
|
78
|
+
rec = Plane.find(2)
|
79
|
+
rec.country = 'England'
|
80
|
+
rec.save
|
81
|
+
|
82
|
+
rec = Plane.find(2)
|
83
|
+
|
84
|
+
assert_equal('England', rec.country)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_update_record_002
|
78
88
|
rec = Plane.find(1)
|
79
89
|
rec.speed = 405
|
80
90
|
rec.save
|
@@ -84,14 +94,23 @@ class TestTable < Test::Unit::TestCase
|
|
84
94
|
assert_equal(rec.speed, 405)
|
85
95
|
end
|
86
96
|
|
87
|
-
def
|
97
|
+
def find_001
|
88
98
|
rec = Plane.find(:first) { |plane| plane.name == 'P-51' }
|
89
99
|
|
90
100
|
assert(rec.is_a?(Plane))
|
91
101
|
assert_equal(rec.name, 'P-51')
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_find_001
|
105
|
+
find_001
|
106
|
+
|
107
|
+
@db.close
|
108
|
+
@db = Mongoose::Database.new(:path => Dir.tmpdir)
|
109
|
+
|
110
|
+
find_001
|
92
111
|
end
|
93
112
|
|
94
|
-
def
|
113
|
+
def find_002
|
95
114
|
recs = Plane.find { |plane| plane.name == 'P-51' }
|
96
115
|
|
97
116
|
assert_equal(recs.size, 1)
|
@@ -105,7 +124,16 @@ class TestTable < Test::Unit::TestCase
|
|
105
124
|
assert_equal(recs[1].name, 'Spitfire')
|
106
125
|
end
|
107
126
|
|
108
|
-
def
|
127
|
+
def test_find_002
|
128
|
+
find_002
|
129
|
+
|
130
|
+
@db.close
|
131
|
+
@db = Mongoose::Database.new(:path => Dir.tmpdir)
|
132
|
+
|
133
|
+
find_002
|
134
|
+
end
|
135
|
+
|
136
|
+
def find_003
|
109
137
|
recs = Plane.find do |plane|
|
110
138
|
plane.any do
|
111
139
|
plane.country == 'USA'
|
@@ -135,7 +163,16 @@ class TestTable < Test::Unit::TestCase
|
|
135
163
|
assert_equal(recs.first.name, 'P-51')
|
136
164
|
end
|
137
165
|
|
138
|
-
def
|
166
|
+
def test_find_003
|
167
|
+
find_003
|
168
|
+
|
169
|
+
@db.close
|
170
|
+
@db = Mongoose::Database.new(:path => Dir.tmpdir)
|
171
|
+
|
172
|
+
find_003
|
173
|
+
end
|
174
|
+
|
175
|
+
def find_004
|
139
176
|
recs = Plane.find { |plane| plane.name.one_of('P-51', 'ME-109') }
|
140
177
|
assert_equal(recs.size, 2)
|
141
178
|
|
@@ -156,6 +193,15 @@ class TestTable < Test::Unit::TestCase
|
|
156
193
|
assert_equal(2, recs.size)
|
157
194
|
end
|
158
195
|
|
196
|
+
def test_find_004
|
197
|
+
find_004
|
198
|
+
|
199
|
+
@db.close
|
200
|
+
@db = Mongoose::Database.new(:path => Dir.tmpdir)
|
201
|
+
|
202
|
+
find_004
|
203
|
+
end
|
204
|
+
|
159
205
|
def test_destroy
|
160
206
|
rec = Plane.find(:first) { |plane| plane.name == 'P-51' }
|
161
207
|
rec.destroy
|