mongoose 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,568 @@
1
+ module Mongoose
2
+
3
+ #-------------------------------------------------------------------------------
4
+ # Table class
5
+ #-------------------------------------------------------------------------------
6
+ class Table
7
+ #-----------------------------------------------------------------------------
8
+ # Table.db
9
+ #-----------------------------------------------------------------------------
10
+ def self.db
11
+ @@db
12
+ end
13
+
14
+ #-----------------------------------------------------------------------------
15
+ # Table.db=
16
+ #-----------------------------------------------------------------------------
17
+ def self.db=(db)
18
+ @@db = db
19
+ end
20
+
21
+ #-----------------------------------------------------------------------------
22
+ # Table.query
23
+ #-----------------------------------------------------------------------------
24
+ def self.query
25
+ self.db.tables[self][:query]
26
+ end
27
+
28
+ #-----------------------------------------------------------------------------
29
+ # Table.table_name
30
+ #-----------------------------------------------------------------------------
31
+ def self.table_name
32
+ self.db.tables[self][:table_name]
33
+ end
34
+
35
+ #-----------------------------------------------------------------------------
36
+ # Table.columns
37
+ #-----------------------------------------------------------------------------
38
+ def self.columns
39
+ self.db.tables[self][:columns]
40
+ end
41
+
42
+ #-----------------------------------------------------------------------------
43
+ # Table.column_names
44
+ #-----------------------------------------------------------------------------
45
+ def self.column_names
46
+ self.db.tables[self][:columns].collect { |c| c.name }
47
+ end
48
+
49
+ #-----------------------------------------------------------------------------
50
+ # Table.path
51
+ #-----------------------------------------------------------------------------
52
+ def self.path
53
+ self.db.path
54
+ end
55
+
56
+ #-----------------------------------------------------------------------------
57
+ # Table.validates_presence_of
58
+ #-----------------------------------------------------------------------------
59
+ def self.validates_presence_of(*col_names)
60
+ define_method(:required?) do |col_name|
61
+ col_names.include?(col_name)
62
+ end
63
+ end
64
+
65
+ #-----------------------------------------------------------------------------
66
+ # Table.has_many
67
+ #-----------------------------------------------------------------------------
68
+ def self.has_many(kind)
69
+ table_name = Util.singularize(kind.to_s).to_sym
70
+ class_name = Util.us_case_to_class_case(table_name.to_s)
71
+ col = Util.col_name_for_class(self.to_s)
72
+
73
+ define_method(kind.to_sym) do
74
+ klass = Object.const_get(class_name)
75
+ Collection.new(self, klass.find { |r|
76
+ r.send(col) == self.instance_eval { @id } })
77
+ end
78
+ end
79
+
80
+ #-----------------------------------------------------------------------------
81
+ # Table.has_one
82
+ #-----------------------------------------------------------------------------
83
+ def self.has_one(kind)
84
+ table_name = kind.to_sym
85
+ class_name = Util.us_case_to_class_case(table_name.to_s)
86
+ col = Util.col_name_for_class(self.to_s)
87
+
88
+ define_method(kind.to_sym) do
89
+ klass = Object.const_get(class_name)
90
+ klass.find(:first) { |r| r.send(col) == self.instance_eval { @id } }
91
+ end
92
+ end
93
+
94
+ #-----------------------------------------------------------------------------
95
+ # Table.belongs_to
96
+ #-----------------------------------------------------------------------------
97
+ def self.belongs_to(kind)
98
+ class_name = Util.us_case_to_class_case(kind.to_sym)
99
+
100
+ define_method(kind) do
101
+ klass = Object.const_get(class_name.to_s)
102
+ klass.find(send("#{kind}_id".to_sym))
103
+ end
104
+
105
+ define_method("#{kind}=".to_sym) do |other|
106
+ other.save
107
+ send("#{kind}_id=".to_sym, other.id.to_i)
108
+ save
109
+ end
110
+ end
111
+
112
+ #-----------------------------------------------------------------------------
113
+ # Table.last_id_used
114
+ #-----------------------------------------------------------------------------
115
+ def self.last_id_used
116
+ self.db.tables[self][:last_id_used]
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
124
+ end
125
+
126
+ #-----------------------------------------------------------------------------
127
+ # Table.deleted_recs_counter
128
+ #-----------------------------------------------------------------------------
129
+ def self.deleted_recs_counter
130
+ self.db.tables[self][:deleted_recs_counter]
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)
138
+ end
139
+
140
+ #-----------------------------------------------------------------------------
141
+ # Table.read_header
142
+ #-----------------------------------------------------------------------------
143
+ def self.read_header
144
+ YAML.load(File.open(File.join(self.path, self.table_name.to_s +
145
+ TBL_HDR_EXT), 'r'))
146
+ end
147
+
148
+ #-----------------------------------------------------------------------------
149
+ # Table.write_header
150
+ #-----------------------------------------------------------------------------
151
+ def self.write_header(header)
152
+ File.open(File.join(self.path, self.table_name.to_s + TBL_HDR_EXT), 'w') do |f|
153
+ YAML.dump(header, f)
154
+ end
155
+ end
156
+
157
+ #-----------------------------------------------------------------------------
158
+ # Table.init_table
159
+ #-----------------------------------------------------------------------------
160
+ def self.init_table
161
+
162
+ tbl_header = self.read_header
163
+
164
+ self.last_id_used = tbl_header[:last_id_used]
165
+ self.deleted_recs_counter = tbl_header[:deleted_recs_counter]
166
+
167
+ self.read_header[:columns].each do |c|
168
+ self.init_column(c[:name], c[:data_type], Object.full_const_get(c[:class])
169
+ )
170
+ end
171
+ end
172
+
173
+ #-----------------------------------------------------------------------------
174
+ # Table.init_column
175
+ #-----------------------------------------------------------------------------
176
+ def self.init_column(col_name, col_def, col_class)
177
+ col = col_class.create(self, col_name, col_def)
178
+
179
+ self.columns << col
180
+
181
+ meth = <<-END_OF_STRING
182
+ def self.#{col_name}
183
+ self.columns.detect { |c| c.name == "#{col_name}".to_sym }
184
+ end
185
+ END_OF_STRING
186
+ self.class_eval(meth)
187
+
188
+ self.class_eval do
189
+ attr_accessor col_name
190
+ end
191
+
192
+ col.init_index if col.indexed?
193
+ end
194
+
195
+ #-----------------------------------------------------------------------------
196
+ # Table.close
197
+ #-----------------------------------------------------------------------------
198
+ def self.close
199
+ self.columns.each { |c| c.close }
200
+ end
201
+
202
+ #-----------------------------------------------------------------------------
203
+ # Table.get_all_recs
204
+ #-----------------------------------------------------------------------------
205
+ def self.get_all_recs
206
+ self.with_table do |fptr|
207
+ begin
208
+ while true
209
+ fpos = fptr.tell
210
+ rec_arr = Marshal.load(fptr)
211
+
212
+ yield rec_arr[1..-1], fpos unless rec_arr[0]
213
+ end
214
+ rescue EOFError
215
+ end
216
+ end
217
+ end
218
+
219
+ #-----------------------------------------------------------------------------
220
+ # Table.add_column
221
+ #-----------------------------------------------------------------------------
222
+ def self.add_column(col_name, col_def, col_class=Column)
223
+ self.init_column(col_name, col_def, col_class)
224
+
225
+ tbl_header = self.read_header
226
+
227
+ tbl_header[:columns] << { :name => col_name, :data_type => col_def,
228
+ :class => col_class.to_s }
229
+
230
+ self.write_header(tbl_header)
231
+ end
232
+
233
+ #-----------------------------------------------------------------------------
234
+ # Table.add_indexed
235
+ #-----------------------------------------------------------------------------
236
+ def self.add_indexed_column(col_name, col_def)
237
+ self.add_column(col_name, col_def, SkipListIndexColumn)
238
+ end
239
+
240
+ #-----------------------------------------------------------------------------
241
+ # Table.find
242
+ #-----------------------------------------------------------------------------
243
+ def self.find(*args)
244
+ # If searching for just one id or a group of ids...
245
+ if args[0].is_a?(Integer)
246
+ if args.size == 1
247
+ self.get_rec(args[0])
248
+ else
249
+ args.collect { |a| self.get_rec(a) }
250
+ end
251
+ else
252
+ result = []
253
+ # If passed a query block...
254
+ if block_given?
255
+ self.query.clear
256
+ query_start = true
257
+ sub_q = false
258
+ sub_q_start = false
259
+ sub_q_result = []
260
+
261
+ # Grab the query block
262
+ yield self
263
+
264
+ # Step through the query block...
265
+ self.query.each_with_index do |q,i|
266
+ # If this is the start of an #any sub-block within the main block,
267
+ # mark it as so and start grabbing the sub-block query.
268
+ if q == :any_begin
269
+ sub_q = :true
270
+ sub_q_start = true
271
+ sub_q_result = []
272
+ next
273
+ end
274
+ # If this is the end of an #any sub-block within the main block,
275
+ # mark it as so, and add the sub-block's query results to the current
276
+ # results of the main query.
277
+ if q == :any_end
278
+ sub_q = false
279
+ if query_start
280
+ query_start = false
281
+ result = sub_q_result
282
+ else
283
+ result = result & sub_q_result
284
+ end
285
+ sub_q_result = nil
286
+ next
287
+ end
288
+
289
+ # If currently within a sub-block query, execute and add it's results
290
+ # to the current sub-block query results.
291
+ if sub_q
292
+ if sub_q_start
293
+ sub_q_start = false
294
+ sub_q_result = q[0].send(q[1], *q[2])
295
+ else
296
+ sub_q_result = sub_q_result | q[0].send(q[1], *q[2])
297
+ end
298
+ next
299
+ end
300
+
301
+ # If this is the beginning of the query start a new result set,
302
+ # otherwise, add the results of the current line of the query to
303
+ # the existing result set.
304
+ if query_start
305
+ query_start = false
306
+ result = q[0].send(q[1], *q[2])
307
+ else
308
+ result = result & q[0].send(q[1], *q[2])
309
+ end
310
+ end
311
+ # If did not pass a query block, just grab all of the ids in the table...
312
+ else
313
+ result = self.id.keys
314
+ end
315
+
316
+ # If no matching records found, return nil.
317
+ if result.nil?
318
+ nil
319
+ # If user just wants first record, return first record.
320
+ elsif args[0] == :first
321
+ self.get_rec(result.first)
322
+ # If user specified a paramaters hash, see if they specified a limit and
323
+ # return that many records.
324
+ elsif args[1].is_a?(Hash)
325
+ if args[1].has_key?(:limit)
326
+ if args[1][:limit] == 1
327
+ self.get_rec(result.first)
328
+ else
329
+ result[0...args[1][:limit]].collect { |r| self.get_rec(r) }
330
+ end
331
+ end
332
+ # Otherwise, just return the whole damn result set.
333
+ else
334
+ result.collect { |r| self.get_rec(r) }
335
+ end
336
+ end
337
+ end
338
+
339
+ #-----------------------------------------------------------------------------
340
+ # Table.any
341
+ #-----------------------------------------------------------------------------
342
+ def self.any
343
+ self.query << :any_begin
344
+ yield
345
+ self.query << :any_end
346
+ end
347
+
348
+ #-----------------------------------------------------------------------------
349
+ # Table.get_rec
350
+ #-----------------------------------------------------------------------------
351
+ def self.get_rec(id)
352
+ fpos = self.id[id]
353
+
354
+ return nil if fpos.nil?
355
+ rec_arr = []
356
+
357
+ self.with_table(File::RDONLY) do |fptr|
358
+ fptr.seek(fpos)
359
+ rec_arr = Marshal.load(fptr)
360
+ end
361
+
362
+ raise IndexCorruptError, "Index references deleted record!", caller \
363
+ if rec_arr[0]
364
+
365
+ raise IndexCorruptError, "Index ID does not match table ID!", caller \
366
+ unless rec_arr[1] == id
367
+
368
+ rec = self.new(*rec_arr[1..-1])
369
+ return rec
370
+ end
371
+
372
+ #-----------------------------------------------------------------------------
373
+ # Table.with_table
374
+ #-----------------------------------------------------------------------------
375
+ def self.with_table(access='r')
376
+ begin
377
+ yield fptr = open(File.join(self.db.path, self.table_name.to_s + TBL_EXT),
378
+ access)
379
+ ensure
380
+ fptr.close
381
+ end
382
+ end
383
+
384
+ #-----------------------------------------------------------------------------
385
+ # initialize
386
+ #-----------------------------------------------------------------------------
387
+ def initialize(*values)
388
+ self.class.columns.zip(values).each do |c,v|
389
+ send("#{c.name}=", v)
390
+ end
391
+ end
392
+
393
+ #-----------------------------------------------------------------------------
394
+ # save
395
+ #-----------------------------------------------------------------------------
396
+ def save
397
+ self.class.columns.each do |c|
398
+ raise "Value required for #{c.name}!" if respond_to?('required?') and \
399
+ required?(c.name) and send(c.name).nil?
400
+ raise "Value required for #{c.name}!" if c.required? and send(c.name).nil?
401
+ end
402
+
403
+ if @id.nil?
404
+ tbl_header = self.class.read_header
405
+ self.class.last_id_used += 1
406
+
407
+ append_record(self.class.last_id_used,
408
+ self.class.column_names[1..-1].collect { |col_name| send(col_name) })
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)
413
+ else
414
+ update_record(@id, self.class.columns[1..-1].collect { |c| send(c.name) })
415
+ end
416
+
417
+ return true
418
+ end
419
+
420
+ #-----------------------------------------------------------------------------
421
+ # destroy
422
+ #-----------------------------------------------------------------------------
423
+ def destroy
424
+ fpos_rec_start = self.class.id[@id]
425
+
426
+ self.class.with_table(File::RDWR) do |fptr|
427
+ fptr.seek(fpos_rec_start)
428
+
429
+ rec = Marshal.load(fptr)
430
+
431
+ raise IndexCorruptError, "Index ID does not match table ID!", caller \
432
+ unless rec[1] == @id
433
+
434
+ rec[0] = true
435
+
436
+ write_record(fptr, fpos_rec_start, Marshal.dump(rec))
437
+ increment_deleted_recs_counter
438
+ end
439
+
440
+ self.class.columns.each_with_index do |c,i|
441
+ if i == 0
442
+ c.remove_index_rec(@id)
443
+ elsif c.indexed?
444
+ c.remove_index_rec(send(c.name), @id)
445
+ end
446
+ end
447
+
448
+ freeze
449
+ end
450
+
451
+ #-----------------------------------------------------------------------------
452
+ # Private Methods
453
+ #-----------------------------------------------------------------------------
454
+ private
455
+
456
+ #-----------------------------------------------------------------------------
457
+ # append_record
458
+ #-----------------------------------------------------------------------------
459
+ def append_record(id, values)
460
+ fpos = nil
461
+
462
+ self.class.with_table(File::RDWR) do |fptr|
463
+ fptr.seek(0, IO::SEEK_END)
464
+ fpos = fptr.tell
465
+
466
+ fpos = write_record(fptr, 'end', Marshal.dump([false, id].concat(values)))
467
+ end
468
+
469
+ self.class.columns.each_with_index do |c,i|
470
+ if i == 0
471
+ c.add_index_rec(id, fpos)
472
+ elsif c.indexed?
473
+ c.add_index_rec(values[i-1], id) unless values[i-1].nil?
474
+ end
475
+ end
476
+ end
477
+
478
+ #-----------------------------------------------------------------------------
479
+ # update_record
480
+ #-----------------------------------------------------------------------------
481
+ def update_record(id, values)
482
+ temp_instance = self.class.get_rec(id)
483
+
484
+ fpos_rec_start = self.class.id[id]
485
+
486
+ self.class.with_table(File::RDWR) do |fptr|
487
+ fptr.seek(fpos_rec_start)
488
+
489
+ old_rec = Marshal.load(fptr)
490
+
491
+ new_rec = Marshal.dump(old_rec[0..1].concat(values))
492
+
493
+ raise IndexCorruptError, "Index ID does not match table ID!", caller \
494
+ unless old_rec[1] == id
495
+
496
+ old_rec_length = fptr.tell - fpos_rec_start
497
+
498
+ if new_rec.length == old_rec_length
499
+ write_record(fptr, fpos_rec_start, new_rec)
500
+ else
501
+ fpos = write_record(fptr, 'end', new_rec)
502
+ increment_deleted_recs_counter
503
+ self.class.columns[0].add_index_rec(id, fpos)
504
+ end
505
+ end
506
+
507
+ self.class.columns[1..-1].each do |c|
508
+ unless temp_instance.send(c.name) == send(c.name)
509
+ if c.indexed?
510
+ c.remove_index_rec(temp_instance.send(c.name), id)
511
+ c.add_index_rec(send(c.name), id) unless send(c.name).nil?
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ #-----------------------------------------------------------------------------
518
+ # increment_deleted_recs_counter
519
+ #-----------------------------------------------------------------------------
520
+ def increment_deleted_recs_counter
521
+ tbl_header = self.class.read_header
522
+ self.class.deleted_recs_counter += 1
523
+ tbl_header[:deleted_recs_counter] = self.class.deleted_recs_counter
524
+ self.class.write_header(tbl_header)
525
+ end
526
+
527
+ #-----------------------------------------------------------------------------
528
+ # write_record
529
+ #-----------------------------------------------------------------------------
530
+ def write_record(fptr, pos, record)
531
+ if pos == 'end'
532
+ fptr.seek(0, IO::SEEK_END)
533
+ else
534
+ fptr.seek(pos)
535
+ end
536
+
537
+ fpos_rec_start = fptr.tell
538
+
539
+ fptr.write(record)
540
+ return fpos_rec_start
541
+ end
542
+ end
543
+
544
+
545
+ class Collection
546
+ include Enumerable
547
+
548
+ def initialize(owner=nil, records=[])
549
+ @owner = owner
550
+ @records = records
551
+ end
552
+
553
+ def each
554
+ records.each { |rec| yield rec }
555
+ end
556
+
557
+ def <<(rec)
558
+ col_name = (@owner.class.table_name.to_s + "_id").to_sym
559
+ rec.send("#{col_name}=".to_sym, @owner.instance_eval { @id })
560
+ rec.save
561
+ end
562
+
563
+ def append(rec)
564
+ self << rec
565
+ end
566
+ end
567
+
568
+ end