Class: Mongoose::Table

Inherits:
Object show all
Defined in:
lib/mongoose/table.rb

Overview


Table class


Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(values = nil) ⇒ Table


initialize




577
578
579
580
581
582
583
# File 'lib/mongoose/table.rb', line 577

def initialize(values=nil)
  unless values.nil?
    values.each do |k,v|
      send("#{k}=", v) if self.class.column_names.include? k
    end
  end
end

Class Method Details

.add_column(col_name, col_def, col_class = Column) ⇒ Object


Table.add_column




232
233
234
235
236
237
238
239
240
241
# File 'lib/mongoose/table.rb', line 232

def self.add_column(col_name, col_def, col_class=Column)
  self.init_column(col_name, col_def, col_class)

  tbl_header = self.read_header

  tbl_header[:columns] << { :name => col_name, :data_type => col_def, 
                        :class => col_class.to_s }

  self.write_header(tbl_header)
end

.add_indexed_column(col_name, col_def) ⇒ Object


Table.add_indexed




246
247
248
# File 'lib/mongoose/table.rb', line 246

def self.add_indexed_column(col_name, col_def)
  self.add_column(col_name, col_def, SkipListIndexColumn)
end

.apply_options_to_result(result, options) ⇒ Object


Table.apply_options_to_result




404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/mongoose/table.rb', line 404

def self.apply_options_to_result(result, options)
  return result if result.empty?

  result = self.sort_result(result, *options[:order]) if options.has_key?(
   :order)
  result = result[options[:offset]-1..-1] if options.has_key?(:offset)
  
  if options.has_key?(:limit)
    if options[:limit] == 1
      result = result.first
    else
      result = result[0...options[:limit]]
    end
  end
  return result
end

.belongs_to(kind) ⇒ Object


Table.belongs_to




114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/mongoose/table.rb', line 114

def self.belongs_to(kind)
  table_name = kind.to_sym
  class_name = Util.us_case_to_class_case(table_name)

  define_method(kind) do
    klass = Object.const_get(class_name.to_s)
    klass.find(send("#{kind}_id".to_sym))
  end

  define_method("#{kind}=".to_sym) do |other|
    other.save
    send("#{kind}_id=".to_sym, other.id.to_i)
    save
  end
end

.closeObject


Table.close




208
209
210
# File 'lib/mongoose/table.rb', line 208

def self.close
  self.columns.each { |c| c.close }
end

.column_namesObject


Table.column_names




53
54
55
# File 'lib/mongoose/table.rb', line 53

def self.column_names
  self.db.tables[self][:columns].collect { |c| c.name }
end

.columnsObject


Table.columns




38
39
40
# File 'lib/mongoose/table.rb', line 38

def self.columns
  self.db.tables[self][:columns]
end

.content_columnsObject


Table.content_columns




45
46
47
48
# File 'lib/mongoose/table.rb', line 45

def self.content_columns
  self.db.tables[self][:columns] = self.columns.reject { |c| 
   c.name == :id || c.name =~ /(_id|_count)$/ }
end

.create(options = {}) ⇒ Object


Table.create




253
254
255
256
257
258
259
260
# File 'lib/mongoose/table.rb', line 253

def self.create(options={})
  rec = new
  options.each do |k,v|
    rec.send("#{k}=", v) if self.column_names.include? k
  end
  rec.save
  rec
end

.dbObject


Table.db




10
11
12
# File 'lib/mongoose/table.rb', line 10

def self.db
  @@db
end

.db=(db) ⇒ Object


Table.db=




17
18
19
# File 'lib/mongoose/table.rb', line 17

def self.db=(db)
  @@db = db
end

.delete(id) ⇒ Object


Table.delete




570
571
572
# File 'lib/mongoose/table.rb', line 570

def self.delete(id)
  self.find(id).delete
end

.delete_all(&block) ⇒ Object


Table.delete_all




563
564
565
# File 'lib/mongoose/table.rb', line 563

def self.delete_all(&block)
  self.find(:all, &block).each { |r| p r.delete }
end

.deleted_recs_counterObject


Table.deleted_recs_counter




140
141
142
# File 'lib/mongoose/table.rb', line 140

def self.deleted_recs_counter
  self.class.read_header[:deleted_recs_counter]
end

.destroy(id) ⇒ Object


Table.destroy




556
557
558
# File 'lib/mongoose/table.rb', line 556

def self.destroy(id)
  self.find(id).destroy
end

.destroy_all(&block) ⇒ Object


Table.destroy_all




549
550
551
# File 'lib/mongoose/table.rb', line 549

def self.destroy_all(&block)
  self.find(:all, &block).each { |r| p r.destroy }
end

.exists?(id) ⇒ Boolean


Table.exists?


Returns:

  • (Boolean)


538
539
540
541
542
543
544
# File 'lib/mongoose/table.rb', line 538

def self.exists?(id)
  if self.id[id]
    true
  else
    false
  end
end

.export(filename = 1) ⇒ Object


Table.export




499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/mongoose/table.rb', line 499

def self.export(filename=1)
  if filename.is_a?(Integer)
    out_file = IO.open(1, 'w')
  else
    out_file = File.open(filename, 'w')
  end
  CSV::Writer.generate(out_file) do |out|
    self.find.each do |rec|
      out << self.column_names.collect {|n| rec.send(n)}
    end
  end

  out_file.close
end

.find(*args, &block) ⇒ Object


Table.find




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
# File 'lib/mongoose/table.rb', line 265

def self.find(*args, &block)
  options = {}
  if args.size == 0
    args = [:all]
  elsif args.first.is_a?(Hash)
    options = args.first
    args = [:all]
  elsif args.first.is_a?(Integer)
    if args.last.is_a?(Hash)
      options = args.last
      args = args[0...-1]
    end
  elsif args.first == :first
    options = args.last if args.last.is_a?(Hash)
    options[:limit] = 1
    args = [:all]
  elsif args.first == :all
    options = args.last if args.last.is_a?(Hash)
  end

  case args.first
    when :all   then self.find_every(options, &block)
    else             self.find_from_ids(args, options)
  end
end

.find_every(options, &block) ⇒ Object


Table.find_every




307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/mongoose/table.rb', line 307

def self.find_every(options, &block)
  # If no block was supplied, just grab all the keys from the id column's
  # index.
  if block
    result = self.find_from_block(&block)
  else
    result = self.id.keys
  end
  return nil if result.nil?

  return self.apply_options_to_result(
   result.collect { |k| self.get_rec(k) }, options)
end

.find_from_block(&block) ⇒ Object


Table.find_from_block




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
# File 'lib/mongoose/table.rb', line 324

def self.find_from_block(&block)
  result = []
  or_result = []
  query = Query.new
  query.find(&block)

  subquery_no = nil
  subquery_type = nil

  # Step through the query block...
  query.predicates.each do |pred|
    # Retain the previous subquery_no and subquery_type.  This will help 
    # determine if I am still in an #any block or just finished an #any block.
    previous_subquery_no = subquery_no
    previous_subquery_type = subquery_type
    subquery_no = pred.subquery_no
    subquery_type = pred.subquery_type

    # If subquery number has not changed, must be in the middle of an #any 
    # block.  Therefore, we are going to do a union of the the comparison's 
    # results to the current or_result.
    if previous_subquery_no == subquery_no
      or_result = or_result | send(pred.property_name).send(pred.comparison, 
       *pred.arg)
    # Otherwise, we are starting either a new :and predicate or a new #any 
    # block.
    else
      # Therefore, the first thing we want to check if the previous subquery 
      #  was an #any block, and add it's result to the overall result array.
      if previous_subquery_type == :or
        # If the previous subquery was an #any block and it was the first
        # subquery in the main query block, initialize the result array
        # to the whole subqquery's result.
        if previous_subquery_no == 1
          result = or_result
        # Otherwise, just do an intersection between the or_result and the 
        # overall result.
        else
          result = result & or_result
        end
      end
      # If the subquery type is :and, then we are just going to add it
      # to the existing result.
      if subquery_type == :and
        # If this is the first subquery, then we just make the overall
        # result equal to the comparison's result
        if subquery_no == 1
          result = send(pred.property_name).send(pred.comparison, *pred.arg) 
        # Otherwise, we are going to do an intersection on the
        # comparison's result and the overall result.
        else
          result = result & send(pred.property_name).send(pred.comparison, 
           *pred.arg)
        end
      # If the subquery type is :or, and it the subquery number is not
      # equal to the previous subquery number, then we know we are 
      # at the first predicate of an #any block and we can initialize the
      # the subquery's result array to whatever the subquery returns.
      else
        or_result = send(pred.property_name).send(pred.comparison, *pred.arg)
      end
    end
  end
  # Now that we are doing executing the whole query, we need to check if
  # the last subquery was an #any block, so that we can make sure the
  # results of this subquery get added into the overall query results.
  if subquery_type == :or
    if subquery_no == 1
      result = or_result
    else
      result = result & or_result
    end
  end

  return result
end

.find_from_ids(args, options) ⇒ Object


Table.find_from_ids




294
295
296
297
298
299
300
301
302
# File 'lib/mongoose/table.rb', line 294

def self.find_from_ids(args, options)
  if args.size == 1
    result = self.get_rec(args.first)
  else
    result = self.apply_options_to_result(args.collect { |a| 
     self.get_rec(a) }, options)
  end
  return result
end

.get_all_recsObject


Table.get_all_recs




215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/mongoose/table.rb', line 215

def self.get_all_recs
  self.with_table do |fptr|
    begin
      while true
        fpos = fptr.tell
        rec_arr = Marshal.load(fptr)

        yield rec_arr[1..-1], fpos unless rec_arr[0]
      end
    rescue EOFError
    end
  end
end

.get_rec(id) ⇒ Object


Table.get_rec


Raises:

  • (IndexCorruptError)


463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/mongoose/table.rb', line 463

def self.get_rec(id)
  fpos = self.id[id]

  return nil if fpos.nil?
  rec_arr = []

  self.with_table(File::RDONLY) do |fptr|
    fptr.seek(fpos)
    rec_arr = Marshal.load(fptr)
  end      

  raise IndexCorruptError, "Index references deleted record!", caller \
   if rec_arr[0]

  raise IndexCorruptError, "Index ID does not match table ID!", caller \
   unless rec_arr[1] == id

  rec = self.new(Hash[*self.column_names.zip(rec_arr[1..-1]).flatten])
  return rec
end

.has_many(kind) ⇒ Object


Table.has_many




84
85
86
87
88
89
90
91
92
93
94
# File 'lib/mongoose/table.rb', line 84

def self.has_many(kind)
  table_name = Util.singularize(kind.to_s).to_sym
  class_name = Util.us_case_to_class_case(table_name)
  col = Util.col_name_for_class(self.to_s)

  define_method(kind.to_sym) do 
    klass = Object.const_get(class_name)
    parent_id = @id
    Collection.new(self, klass.find { |r| r.send(col) == parent_id })
  end
end

.has_one(kind) ⇒ Object


Table.has_one




99
100
101
102
103
104
105
106
107
108
109
# File 'lib/mongoose/table.rb', line 99

def self.has_one(kind)
  table_name = kind.to_sym
  class_name = Util.us_case_to_class_case(table_name)
  col = Util.col_name_for_class(self.to_s)

  define_method(kind.to_sym) do
    klass = Object.const_get(class_name)
    parent_id = @id
    klass.find(:first) { |r| r.send(col) == parent_id }
  end
end

.import(filename = 0) ⇒ Object


Table.import




517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/mongoose/table.rb', line 517

def self.import(filename=0)
  if filename.is_a?(Integer)
    in_file = IO.open(1, 'r')
  else
    in_file = File.open(filename, 'r')
  end

  CSV::Reader.parse(in_file) do |row|
    rec = new
    self.columns.zip(row) do |col, value|
      rec.send("#{col.name}=", col.convert_to_native(value)) unless \
       value.nil?
    end
    rec.save
  end
  in_file.close
end

.init_column(col_name, col_def, col_class) ⇒ Object


Table.init_column




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
# File 'lib/mongoose/table.rb', line 175

def self.init_column(col_name, col_def, col_class)
  col = col_class.create(self, col_name, col_def)

  self.columns << col

  (class << self; self; end).class_eval do 
    define_method(col_name) do
      self.columns.detect { |c| c.name == col_name.to_sym }
    end

    define_method("find_by_#{col_name}".to_sym) do |other|
      if col_name == :id
        self.find(other)
      else
        self.find(:first) { |tbl| tbl.send(col_name) == other }
      end
    end

    define_method("find_all_by_#{col_name}".to_sym) do |other, *args|
      self.find(*args) { |tbl| tbl.send(col_name) == other }
    end
  end

  self.class_eval do
    attr_accessor col_name
  end

  col.init_index if col.indexed?
end

.init_tableObject


Table.init_table




163
164
165
166
167
168
169
170
# File 'lib/mongoose/table.rb', line 163

def self.init_table
  tbl_header = self.read_header

  self.read_header[:columns].each do |c| 
    self.init_column(c[:name], c[:data_type], Object.full_const_get(c[:class])
     ) 
  end
end

.last_id_usedObject


Table.last_id_used




133
134
135
# File 'lib/mongoose/table.rb', line 133

def self.last_id_used
  self.class.read_header[:last_id_used]
end

.pathObject


Table.path




60
61
62
# File 'lib/mongoose/table.rb', line 60

def self.path
  self.db.path
end

.plural_form(pluralized) ⇒ Object


Table.plural_form




67
68
69
70
# File 'lib/mongoose/table.rb', line 67

def self.plural_form(pluralized)
  Util::SINGULAR_TO_PLURAL[Util.class_case_to_us_case(self.to_s)] = pluralized
  Util::PLURAL_TO_SINGULAR[pluralized] = Util.class_case_to_us_case(self.to_s)
end

.queryObject


Table.query




24
25
26
# File 'lib/mongoose/table.rb', line 24

def self.query
  self.db.tables[self][:query]
end

.read_headerObject


Table.read_header




147
148
149
150
# File 'lib/mongoose/table.rb', line 147

def self.read_header
  YAML.load(File.open(File.join(self.path, self.table_name.to_s +
   TBL_HDR_EXT), 'r'))
end

.sort_result(result, *order) ⇒ Object


Table.sort_result




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
# File 'lib/mongoose/table.rb', line 424

def self.sort_result(result, *order)
  sort_cols_arrs = []
  order.each do |sort_col|
    if sort_col.to_s[0..0] == '-'
      sort_cols_arrs << [sort_col.to_s[1..-1].to_sym, :desc]
    elsif sort_col.to_s[0..0] == '+'
      sort_cols_arrs << [sort_col.to_s[1..-1].to_sym, :asc]
    else
      sort_cols_arrs << [sort_col, :asc]
    end
  end

  return result.sort do |a,b|
    x = []
    y = []
    sort_cols_arrs.each do |s|
      if [:integer, :float].include?(send(s.first).data_type)
        a_value = a.send(s.first) || 0
        b_value = b.send(s.first) || 0
      else
        a_value = a.send(s.first)
        b_value = b.send(s.first)
      end
      if s.last == :desc
        x << b_value
        y << a_value
      else
        x << a_value
        y << b_value
      end
    end

    x <=> y
  end
end

.table_nameObject


Table.table_name




31
32
33
# File 'lib/mongoose/table.rb', line 31

def self.table_name
  self.db.tables[self][:table_name]
end

.validates_presence_of(*col_names) ⇒ Object


Table.validates_presence_of




75
76
77
78
79
# File 'lib/mongoose/table.rb', line 75

def self.validates_presence_of(*col_names)
  define_method(:required?) do |col_name|
    col_names.include?(col_name)
  end
end

.with_table(access = 'r') ⇒ Object


Table.with_table




487
488
489
490
491
492
493
494
# File 'lib/mongoose/table.rb', line 487

def self.with_table(access='r')
  begin
    yield fptr = open(File.join(self.db.path, self.table_name.to_s + TBL_EXT), 
     access)
  ensure
    fptr.close
  end
end

.write_header(header) ⇒ Object


Table.write_header




155
156
157
158
# File 'lib/mongoose/table.rb', line 155

def self.write_header(header)
  File.open(File.join(self.path, self.table_name.to_s + TBL_HDR_EXT), 'w'
   ) { |f| YAML.dump(header, f) }
end

Instance Method Details

#deleteObject


delete




621
622
623
# File 'lib/mongoose/table.rb', line 621

def delete
  destroy
end

#destroyObject


destroy




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
# File 'lib/mongoose/table.rb', line 628

def destroy
  fpos_rec_start = self.class.id[@id]

  self.class.with_table(File::RDWR) do |fptr|
    fptr.seek(fpos_rec_start)

    rec = Marshal.load(fptr)
    
    raise IndexCorruptError, "Index ID does not match table ID!", caller \
     unless rec[1] == @id

    # First array position of record is the deleted flag:  true means deleted
    rec[0] = true

    # Record is not actually deleted; it just has its deleted flag set to
    # true.
    write_record(fptr, fpos_rec_start, Marshal.dump(rec))
    increment_deleted_recs_counter
  end

  # Remove all index recs pointing to this record.
  self.class.columns.each_with_index do |c,i|
    if i == 0
      c.remove_index_rec(@id)
    elsif c.indexed?
      c.remove_index_rec(send(c.name), @id)
    end
  end

  # Don't allow any more changes to this record.
  freeze
end

#saveObject


save




598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/mongoose/table.rb', line 598

def save
  self.class.columns.each do |c|
    # First checks to see if validates_presence_of was set in class def.
    raise "Value required for #{c.name}!" if respond_to?('required?') and \
     required?(c.name) and send(c.name).nil?
    # Next checks to see if validates_presence_of was set in #add_column.
    raise "Value required for #{c.name}!" if c.required? and send(c.name).nil?
  end

  # Add new record.
  if @id.nil?
    @id = append_record(self.class.column_names[1..-1].collect { |c_name| 
     send(c_name) })
  # Update existing record.
  else
    update_record(@id, self.class.columns[1..-1].collect { |c| send(c.name) })
  end
  return true
end

#update_attributes(values) ⇒ Object


update_attributes




588
589
590
591
592
593
# File 'lib/mongoose/table.rb', line 588

def update_attributes(values)
  values.each do |k,v|
    send("#{k}=", v) if self.class.column_names.include? k
  end
  save
end