mongoose 0.2.0 → 0.2.5
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.
- data/README +13 -10
- data/bin/mongoose_export.rb +17 -0
- data/bin/mongoose_import.rb +17 -0
- data/changes.txt +24 -0
- data/example/simple_examples.rb +63 -26
- data/lib/mongoose.rb +43 -3
- data/lib/mongoose/column.rb +147 -64
- data/lib/mongoose/error.rb +9 -0
- data/lib/mongoose/query.rb +52 -0
- data/lib/mongoose/table.rb +318 -97
- data/lib/mongoose/util.rb +21 -8
- data/test/tc_relations.rb +28 -1
- data/test/tc_table.rb +145 -25
- metadata +9 -4
@@ -0,0 +1,52 @@
|
|
1
|
+
module Mongoose
|
2
|
+
|
3
|
+
class Property
|
4
|
+
attr_reader :property_name, :subquery_no, :subquery_type, :comparison, :arg
|
5
|
+
def initialize(name, query, subquery_no, subquery_type)
|
6
|
+
@property_name = name
|
7
|
+
@query = query
|
8
|
+
@subquery_no = subquery_no
|
9
|
+
@subquery_type = subquery_type
|
10
|
+
end
|
11
|
+
|
12
|
+
[:>, :<=, :==, :<=, :<, :between, :one_of].each do |comparison|
|
13
|
+
define_method(comparison) do |*arg|
|
14
|
+
@comparison = comparison
|
15
|
+
@arg = arg
|
16
|
+
@query.add_predicate(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
class Query
|
24
|
+
attr_reader :predicates
|
25
|
+
def initialize
|
26
|
+
@predicates = []
|
27
|
+
@subquery_type = :and
|
28
|
+
@subquery_no = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def find(&block)
|
32
|
+
yield self
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_predicate(pred)
|
36
|
+
@predicates << pred
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(name, *args)
|
40
|
+
@subquery_no += 1 if @subquery_type == :and
|
41
|
+
Property.new(name, self, @subquery_no, @subquery_type)
|
42
|
+
end
|
43
|
+
|
44
|
+
def any(&block)
|
45
|
+
@subquery_type = :or
|
46
|
+
@subquery_no += 1
|
47
|
+
yield
|
48
|
+
@subquery_type = :and
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/mongoose/table.rb
CHANGED
@@ -39,6 +39,14 @@ class Table
|
|
39
39
|
self.db.tables[self][:columns]
|
40
40
|
end
|
41
41
|
|
42
|
+
#-----------------------------------------------------------------------------
|
43
|
+
# Table.content_columns
|
44
|
+
#-----------------------------------------------------------------------------
|
45
|
+
def self.content_columns
|
46
|
+
self.db.tables[self][:columns] = self.columns.reject { |c|
|
47
|
+
c.name == :id || c.name =~ /(_id|_count)$/ }
|
48
|
+
end
|
49
|
+
|
42
50
|
#-----------------------------------------------------------------------------
|
43
51
|
# Table.column_names
|
44
52
|
#-----------------------------------------------------------------------------
|
@@ -53,6 +61,14 @@ class Table
|
|
53
61
|
self.db.path
|
54
62
|
end
|
55
63
|
|
64
|
+
#-----------------------------------------------------------------------------
|
65
|
+
# Table.plural_form
|
66
|
+
#-----------------------------------------------------------------------------
|
67
|
+
def self.plural_form(pluralized)
|
68
|
+
Util::SINGULAR_TO_PLURAL[Util.class_case_to_us_case(self.to_s)] = pluralized
|
69
|
+
Util::PLURAL_TO_SINGULAR[pluralized] = Util.class_case_to_us_case(self.to_s)
|
70
|
+
end
|
71
|
+
|
56
72
|
#-----------------------------------------------------------------------------
|
57
73
|
# Table.validates_presence_of
|
58
74
|
#-----------------------------------------------------------------------------
|
@@ -73,7 +89,7 @@ class Table
|
|
73
89
|
define_method(kind.to_sym) do
|
74
90
|
klass = Object.const_get(class_name)
|
75
91
|
parent_id = @id
|
76
|
-
Collection.new(self, klass.find { send(col) == parent_id })
|
92
|
+
Collection.new(self, klass.find { |r| r.send(col) == parent_id })
|
77
93
|
end
|
78
94
|
end
|
79
95
|
|
@@ -88,7 +104,7 @@ class Table
|
|
88
104
|
define_method(kind.to_sym) do
|
89
105
|
klass = Object.const_get(class_name)
|
90
106
|
parent_id = @id
|
91
|
-
klass.find(:first) { send(col) == parent_id }
|
107
|
+
klass.find(:first) { |r| r.send(col) == parent_id }
|
92
108
|
end
|
93
109
|
end
|
94
110
|
|
@@ -165,6 +181,18 @@ class Table
|
|
165
181
|
define_method(col_name) do
|
166
182
|
self.columns.detect { |c| c.name == col_name.to_sym }
|
167
183
|
end
|
184
|
+
|
185
|
+
define_method("find_by_#{col_name}".to_sym) do |other|
|
186
|
+
if col_name == :id
|
187
|
+
self.find(other)
|
188
|
+
else
|
189
|
+
self.find(:first) { |tbl| tbl.send(col_name) == other }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
define_method("find_all_by_#{col_name}".to_sym) do |other, *args|
|
194
|
+
self.find(*args) { |tbl| tbl.send(col_name) == other }
|
195
|
+
end
|
168
196
|
end
|
169
197
|
|
170
198
|
self.class_eval do
|
@@ -235,109 +263,199 @@ class Table
|
|
235
263
|
# Table.find
|
236
264
|
#-----------------------------------------------------------------------------
|
237
265
|
def self.find(*args, &block)
|
238
|
-
|
239
|
-
if args
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
266
|
+
options = {}
|
267
|
+
if args.size == 0
|
268
|
+
args = [:all]
|
269
|
+
elsif args.first.is_a?(Hash)
|
270
|
+
options = args.first
|
271
|
+
args = [:all]
|
272
|
+
elsif args.first.is_a?(Integer)
|
273
|
+
if args.last.is_a?(Hash)
|
274
|
+
options = args.last
|
275
|
+
args = args[0...-1]
|
244
276
|
end
|
277
|
+
elsif args.first == :first
|
278
|
+
options = args.last if args.last.is_a?(Hash)
|
279
|
+
options[:limit] = 1
|
280
|
+
args = [:all]
|
281
|
+
elsif args.first == :all
|
282
|
+
options = args.last if args.last.is_a?(Hash)
|
283
|
+
end
|
284
|
+
|
285
|
+
case args.first
|
286
|
+
when :all then self.find_every(options, &block)
|
287
|
+
else self.find_from_ids(args, options)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
#-----------------------------------------------------------------------------
|
292
|
+
# Table.find_from_ids
|
293
|
+
#-----------------------------------------------------------------------------
|
294
|
+
def self.find_from_ids(args, options)
|
295
|
+
if args.size == 1
|
296
|
+
result = self.get_rec(args.first)
|
245
297
|
else
|
246
|
-
result =
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
sub_q = false
|
252
|
-
sub_q_start = false
|
253
|
-
sub_q_result = []
|
254
|
-
|
255
|
-
# Grab the query block
|
256
|
-
instance_eval(&block)
|
257
|
-
|
258
|
-
# Step through the query block...
|
259
|
-
self.query.each_with_index do |q,i|
|
260
|
-
# If this is the start of an #any sub-block within the main block,
|
261
|
-
# mark it as so and start grabbing the sub-block query.
|
262
|
-
if q == :any_begin
|
263
|
-
sub_q = :true
|
264
|
-
sub_q_start = true
|
265
|
-
sub_q_result = []
|
266
|
-
next
|
267
|
-
end
|
268
|
-
# If this is the end of an #any sub-block within the main block,
|
269
|
-
# mark it as so, and add the sub-block's query results to the current
|
270
|
-
# results of the main query.
|
271
|
-
if q == :any_end
|
272
|
-
sub_q = false
|
273
|
-
if query_start
|
274
|
-
query_start = false
|
275
|
-
result = sub_q_result
|
276
|
-
else
|
277
|
-
result = result & sub_q_result
|
278
|
-
end
|
279
|
-
sub_q_result = nil
|
280
|
-
next
|
281
|
-
end
|
298
|
+
result = self.apply_options_to_result(args.collect { |a|
|
299
|
+
self.get_rec(a) }, options)
|
300
|
+
end
|
301
|
+
return result
|
302
|
+
end
|
282
303
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
304
|
+
#-----------------------------------------------------------------------------
|
305
|
+
# Table.find_every
|
306
|
+
#-----------------------------------------------------------------------------
|
307
|
+
def self.find_every(options, &block)
|
308
|
+
# If no block was supplied, just grab all the keys from the id column's
|
309
|
+
# index.
|
310
|
+
if block
|
311
|
+
result = self.find_from_block(&block)
|
312
|
+
else
|
313
|
+
result = self.id.keys
|
314
|
+
end
|
315
|
+
return nil if result.nil?
|
294
316
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
317
|
+
return self.apply_options_to_result(
|
318
|
+
result.collect { |k| self.get_rec(k) }, options)
|
319
|
+
end
|
320
|
+
|
321
|
+
#-----------------------------------------------------------------------------
|
322
|
+
# Table.find_from_block
|
323
|
+
#-----------------------------------------------------------------------------
|
324
|
+
def self.find_from_block(&block)
|
325
|
+
result = []
|
326
|
+
or_result = []
|
327
|
+
query = Query.new
|
328
|
+
query.find(&block)
|
329
|
+
|
330
|
+
subquery_no = nil
|
331
|
+
subquery_type = nil
|
332
|
+
|
333
|
+
# Step through the query block...
|
334
|
+
query.predicates.each do |pred|
|
335
|
+
# Retain the previous subquery_no and subquery_type. This will help
|
336
|
+
# determine if I am still in an #any block or just finished an #any block.
|
337
|
+
previous_subquery_no = subquery_no
|
338
|
+
previous_subquery_type = subquery_type
|
339
|
+
subquery_no = pred.subquery_no
|
340
|
+
subquery_type = pred.subquery_type
|
341
|
+
|
342
|
+
# If subquery number has not changed, must be in the middle of an #any
|
343
|
+
# block. Therefore, we are going to do a union of the the comparison's
|
344
|
+
# results to the current or_result.
|
345
|
+
if previous_subquery_no == subquery_no
|
346
|
+
or_result = or_result | send(pred.property_name).send(pred.comparison,
|
347
|
+
*pred.arg)
|
348
|
+
# Otherwise, we are starting either a new :and predicate or a new #any
|
349
|
+
# block.
|
350
|
+
else
|
351
|
+
# Therefore, the first thing we want to check if the previous subquery
|
352
|
+
# was an #any block, and add it's result to the overall result array.
|
353
|
+
if previous_subquery_type == :or
|
354
|
+
# If the previous subquery was an #any block and it was the first
|
355
|
+
# subquery in the main query block, initialize the result array
|
356
|
+
# to the whole subqquery's result.
|
357
|
+
if previous_subquery_no == 1
|
358
|
+
result = or_result
|
359
|
+
# Otherwise, just do an intersection between the or_result and the
|
360
|
+
# overall result.
|
361
|
+
else
|
362
|
+
result = result & or_result
|
303
363
|
end
|
304
364
|
end
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
elsif args[0] == :first
|
315
|
-
self.get_rec(result.first)
|
316
|
-
# If user specified a paramaters hash, see if they specified a limit and
|
317
|
-
# return that many records.
|
318
|
-
elsif args[1].is_a?(Hash)
|
319
|
-
if args[1].has_key?(:limit)
|
320
|
-
if args[1][:limit] == 1
|
321
|
-
self.get_rec(result.first)
|
365
|
+
# If the subquery type is :and, then we are just going to add it
|
366
|
+
# to the existing result.
|
367
|
+
if subquery_type == :and
|
368
|
+
# If this is the first subquery, then we just make the overall
|
369
|
+
# result equal to the comparison's result
|
370
|
+
if subquery_no == 1
|
371
|
+
result = send(pred.property_name).send(pred.comparison, *pred.arg)
|
372
|
+
# Otherwise, we are going to do an intersection on the
|
373
|
+
# comparison's result and the overall result.
|
322
374
|
else
|
323
|
-
result
|
375
|
+
result = result & send(pred.property_name).send(pred.comparison,
|
376
|
+
*pred.arg)
|
324
377
|
end
|
378
|
+
# If the subquery type is :or, and it the subquery number is not
|
379
|
+
# equal to the previous subquery number, then we know we are
|
380
|
+
# at the first predicate of an #any block and we can initialize the
|
381
|
+
# the subquery's result array to whatever the subquery returns.
|
382
|
+
else
|
383
|
+
or_result = send(pred.property_name).send(pred.comparison, *pred.arg)
|
325
384
|
end
|
326
|
-
|
385
|
+
end
|
386
|
+
end
|
387
|
+
# Now that we are doing executing the whole query, we need to check if
|
388
|
+
# the last subquery was an #any block, so that we can make sure the
|
389
|
+
# results of this subquery get added into the overall query results.
|
390
|
+
if subquery_type == :or
|
391
|
+
if subquery_no == 1
|
392
|
+
result = or_result
|
327
393
|
else
|
328
|
-
result
|
394
|
+
result = result & or_result
|
329
395
|
end
|
330
396
|
end
|
397
|
+
|
398
|
+
return result
|
331
399
|
end
|
332
400
|
|
333
401
|
#-----------------------------------------------------------------------------
|
334
|
-
# Table.
|
402
|
+
# Table.apply_options_to_result
|
335
403
|
#-----------------------------------------------------------------------------
|
336
|
-
def self.
|
337
|
-
|
338
|
-
|
339
|
-
self.
|
340
|
-
|
404
|
+
def self.apply_options_to_result(result, options)
|
405
|
+
return result if result.empty?
|
406
|
+
|
407
|
+
result = self.sort_result(result, *options[:order]) if options.has_key?(
|
408
|
+
:order)
|
409
|
+
result = result[options[:offset]-1..-1] if options.has_key?(:offset)
|
410
|
+
|
411
|
+
if options.has_key?(:limit)
|
412
|
+
if options[:limit] == 1
|
413
|
+
result = result.first
|
414
|
+
else
|
415
|
+
result = result[0...options[:limit]]
|
416
|
+
end
|
417
|
+
end
|
418
|
+
return result
|
419
|
+
end
|
420
|
+
|
421
|
+
#-----------------------------------------------------------------------------
|
422
|
+
# Table.sort_result
|
423
|
+
#-----------------------------------------------------------------------------
|
424
|
+
def self.sort_result(result, *order)
|
425
|
+
sort_cols_arrs = []
|
426
|
+
order.each do |sort_col|
|
427
|
+
if sort_col.to_s[0..0] == '-'
|
428
|
+
sort_cols_arrs << [sort_col.to_s[1..-1].to_sym, :desc]
|
429
|
+
elsif sort_col.to_s[0..0] == '+'
|
430
|
+
sort_cols_arrs << [sort_col.to_s[1..-1].to_sym, :asc]
|
431
|
+
else
|
432
|
+
sort_cols_arrs << [sort_col, :asc]
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
return result.sort do |a,b|
|
437
|
+
x = []
|
438
|
+
y = []
|
439
|
+
sort_cols_arrs.each do |s|
|
440
|
+
if [:integer, :float].include?(send(s.first).data_type)
|
441
|
+
a_value = a.send(s.first) || 0
|
442
|
+
b_value = b.send(s.first) || 0
|
443
|
+
else
|
444
|
+
a_value = a.send(s.first)
|
445
|
+
b_value = b.send(s.first)
|
446
|
+
end
|
447
|
+
if s.last == :desc
|
448
|
+
x << b_value
|
449
|
+
y << a_value
|
450
|
+
else
|
451
|
+
x << a_value
|
452
|
+
y << b_value
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
x <=> y
|
457
|
+
end
|
458
|
+
end
|
341
459
|
|
342
460
|
#-----------------------------------------------------------------------------
|
343
461
|
# Table.get_rec
|
@@ -359,7 +477,7 @@ class Table
|
|
359
477
|
raise IndexCorruptError, "Index ID does not match table ID!", caller \
|
360
478
|
unless rec_arr[1] == id
|
361
479
|
|
362
|
-
rec = self.new(*rec_arr[1..-1])
|
480
|
+
rec = self.new(Hash[*self.column_names.zip(rec_arr[1..-1]).flatten])
|
363
481
|
return rec
|
364
482
|
end
|
365
483
|
|
@@ -375,11 +493,103 @@ class Table
|
|
375
493
|
end
|
376
494
|
end
|
377
495
|
|
496
|
+
#-----------------------------------------------------------------------------
|
497
|
+
# Table.export
|
498
|
+
#-----------------------------------------------------------------------------
|
499
|
+
def self.export(filename=1)
|
500
|
+
if filename.is_a?(Integer)
|
501
|
+
out_file = IO.open(1, 'w')
|
502
|
+
else
|
503
|
+
out_file = File.open(filename, 'w')
|
504
|
+
end
|
505
|
+
CSV::Writer.generate(out_file) do |out|
|
506
|
+
self.find.each do |rec|
|
507
|
+
out << self.column_names.collect {|n| rec.send(n)}
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
out_file.close
|
512
|
+
end
|
513
|
+
|
514
|
+
#-----------------------------------------------------------------------------
|
515
|
+
# Table.import
|
516
|
+
#-----------------------------------------------------------------------------
|
517
|
+
def self.import(filename=0)
|
518
|
+
if filename.is_a?(Integer)
|
519
|
+
in_file = IO.open(1, 'r')
|
520
|
+
else
|
521
|
+
in_file = File.open(filename, 'r')
|
522
|
+
end
|
523
|
+
|
524
|
+
CSV::Reader.parse(in_file) do |row|
|
525
|
+
rec = new
|
526
|
+
self.columns.zip(row) do |col, value|
|
527
|
+
rec.send("#{col.name}=", col.convert_to_native(value)) unless \
|
528
|
+
value.nil?
|
529
|
+
end
|
530
|
+
rec.save
|
531
|
+
end
|
532
|
+
in_file.close
|
533
|
+
end
|
534
|
+
|
535
|
+
#-----------------------------------------------------------------------------
|
536
|
+
# Table.exists?
|
537
|
+
#-----------------------------------------------------------------------------
|
538
|
+
def self.exists?(id)
|
539
|
+
if self.id[id]
|
540
|
+
true
|
541
|
+
else
|
542
|
+
false
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
#-----------------------------------------------------------------------------
|
547
|
+
# Table.destroy_all
|
548
|
+
#-----------------------------------------------------------------------------
|
549
|
+
def self.destroy_all(&block)
|
550
|
+
self.find(:all, &block).each { |r| p r.destroy }
|
551
|
+
end
|
552
|
+
|
553
|
+
#-----------------------------------------------------------------------------
|
554
|
+
# Table.destroy
|
555
|
+
#-----------------------------------------------------------------------------
|
556
|
+
def self.destroy(id)
|
557
|
+
self.find(id).destroy
|
558
|
+
end
|
559
|
+
|
560
|
+
#-----------------------------------------------------------------------------
|
561
|
+
# Table.delete_all
|
562
|
+
#-----------------------------------------------------------------------------
|
563
|
+
def self.delete_all(&block)
|
564
|
+
self.find(:all, &block).each { |r| p r.delete }
|
565
|
+
end
|
566
|
+
|
567
|
+
#-----------------------------------------------------------------------------
|
568
|
+
# Table.delete
|
569
|
+
#-----------------------------------------------------------------------------
|
570
|
+
def self.delete(id)
|
571
|
+
self.find(id).delete
|
572
|
+
end
|
573
|
+
|
378
574
|
#-----------------------------------------------------------------------------
|
379
575
|
# initialize
|
380
576
|
#-----------------------------------------------------------------------------
|
381
|
-
def initialize(
|
382
|
-
|
577
|
+
def initialize(values=nil)
|
578
|
+
unless values.nil?
|
579
|
+
values.each do |k,v|
|
580
|
+
send("#{k}=", v) if self.class.column_names.include? k
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
#-----------------------------------------------------------------------------
|
586
|
+
# update_attributes
|
587
|
+
#-----------------------------------------------------------------------------
|
588
|
+
def update_attributes(values)
|
589
|
+
values.each do |k,v|
|
590
|
+
send("#{k}=", v) if self.class.column_names.include? k
|
591
|
+
end
|
592
|
+
save
|
383
593
|
end
|
384
594
|
|
385
595
|
#-----------------------------------------------------------------------------
|
@@ -396,10 +606,8 @@ class Table
|
|
396
606
|
|
397
607
|
# Add new record.
|
398
608
|
if @id.nil?
|
399
|
-
id =
|
400
|
-
|
401
|
-
send(col_name) })
|
402
|
-
@id = id
|
609
|
+
@id = append_record(self.class.column_names[1..-1].collect { |c_name|
|
610
|
+
send(c_name) })
|
403
611
|
# Update existing record.
|
404
612
|
else
|
405
613
|
update_record(@id, self.class.columns[1..-1].collect { |c| send(c.name) })
|
@@ -407,6 +615,13 @@ class Table
|
|
407
615
|
return true
|
408
616
|
end
|
409
617
|
|
618
|
+
#-----------------------------------------------------------------------------
|
619
|
+
# delete
|
620
|
+
#-----------------------------------------------------------------------------
|
621
|
+
def delete
|
622
|
+
destroy
|
623
|
+
end
|
624
|
+
|
410
625
|
#-----------------------------------------------------------------------------
|
411
626
|
# destroy
|
412
627
|
#-----------------------------------------------------------------------------
|
@@ -451,7 +666,8 @@ class Table
|
|
451
666
|
#-----------------------------------------------------------------------------
|
452
667
|
# append_record
|
453
668
|
#-----------------------------------------------------------------------------
|
454
|
-
def append_record(
|
669
|
+
def append_record(values)
|
670
|
+
id = increment_last_id_used
|
455
671
|
fpos = nil
|
456
672
|
|
457
673
|
self.class.with_table(File::RDWR) do |fptr|
|
@@ -471,6 +687,7 @@ class Table
|
|
471
687
|
c.add_index_rec(values[i-1], id) unless values[i-1].nil?
|
472
688
|
end
|
473
689
|
end
|
690
|
+
return id
|
474
691
|
end
|
475
692
|
|
476
693
|
#-----------------------------------------------------------------------------
|
@@ -481,6 +698,10 @@ class Table
|
|
481
698
|
|
482
699
|
fpos_rec_start = self.class.id[id]
|
483
700
|
|
701
|
+
raise(RecordNotFound,
|
702
|
+
"No #{self.class.table_name} record found with id: #{id}") if \
|
703
|
+
fpos_rec_start.nil?
|
704
|
+
|
484
705
|
self.class.with_table(File::RDWR) do |fptr|
|
485
706
|
fptr.seek(fpos_rec_start)
|
486
707
|
|