Module: M4DBI

Defined in:
lib/m4dbi.rb,
lib/m4dbi/model.rb,
lib/m4dbi/error.rb,
lib/m4dbi/version.rb,
lib/m4dbi/database.rb,
lib/m4dbi/statement.rb,
lib/m4dbi/collection.rb

Defined Under Namespace

Classes: Collection, Database, Error, Model, Statement

Constant Summary

VERSION =
'0.9.0'

Class Method Summary collapse

Class Method Details

.connect(*args) ⇒ Object



21
22
23
24
25
# File 'lib/m4dbi.rb', line 21

def self.connect( *args )
  dbh = M4DBI::Database.new( RDBI.connect( *args ) )
  trait :last_dbh => dbh
  dbh
end

.Model(table, options = Hash.new) ⇒ Object

Define a new M4DBI::Model like this:

class Post < M4DBI::Model( :posts ); end

You can specify the primary key column(s) using an option, like so:

class Author < M4DBI::Model( :authors, pk: [ 'auth_num' ] ); end


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
# File 'lib/m4dbi/model.rb', line 504

def self.Model( table, options = Hash.new )
  h = options[ :dbh ] || M4DBI.last_dbh
  if h.nil? || ! h.connected?
    raise M4DBI::Error.new( "Attempted to create a Model class without first connecting to a database." )
  end
  pk_ = options[ :pk ] || [ 'id' ]
  if not pk_.respond_to? :each
    raise M4DBI::Error.new( "Primary key must be enumerable (was given #{pk_.inspect})" )
  end

  model_key =
    if h.respond_to? :database_name
      "#{h.database_name}::#{table}"
    else
      table
    end

  @models ||= Hash.new
  @models[ model_key ] ||= Class.new( M4DBI::Model ) do |klass|
    klass.trait( {
      :dbh       => h,
      :table     => table,
      :pk        => pk_,
      :columns   => h.table_schema( table.to_sym ).columns,
      :hooks => {
        after_create: [],
        after_update: [],
        before_delete: [],
        after_delete: [],
        active: true,
      },
    } )

    meta_def( 'pk_str'.to_sym ) do
      if pk.size == 1
        pk[ 0 ].to_s
      else
        pk.to_s
      end
    end

    if defined?( RDBI::Driver::PostgreSQL ) && RDBI::Driver::PostgreSQL === h.driver
      # TODO: This is broken for non-SERIAL or multi-column primary keys
      meta_def( "last_record".to_sym ) do |dbh_|
        self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = currval( '#{table}_#{pk_str}_seq' );"
      end
    elsif defined?( RDBI::Driver::MySQL ) && RDBI::Driver::MySQL === h.driver
      meta_def( "last_record".to_sym ) do |dbh_|
        self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = LAST_INSERT_ID();"
      end
    elsif defined?( RDBI::Driver::SQLite3 ) && RDBI::Driver::SQLite3 === h.driver
      meta_def( "last_record".to_sym ) do |dbh_|
        self.s1 "SELECT * FROM #{table} WHERE #{pk_str} = last_insert_rowid();"
      end
    # TODO: more DB drivers
    end

    klass.trait[ :columns ].each do |col|

      colname = col[ 'name' ]
      method = colname.to_sym
      while klass.method_defined? method
        method = "#{method}_".to_sym
      end

      # Column readers
      class_def( method ) do
        @row[ colname ]
      end

      # Column writers

      class_def( "#{method}=".to_sym ) do |new_value|
        state_before = self.to_h
        stm = prepare("UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}")
        num_changed = stm.execute(
          new_value,
          *pk_values
        ).affected_count
        if defined?( RDBI::Driver::PostgreSQL ) && RDBI::Driver::PostgreSQL === h.driver
          stm.finish
        end
        if num_changed > 0
          @row[ colname ] = new_value
        end
        if self.class.hooks[:active]
          self.class.hooks[:after_update].each do |block|
            self.class.hooks[:active] = false
            block.yield state_before, self
            self.class.hooks[:active] = true
          end
        end
        new_value
      end

      class_def( '[]='.to_sym ) do |colname, new_value|
        stm = prepare("UPDATE #{table} SET #{colname} = ? WHERE #{pk_clause}")
        num_changed = stm.execute(
          new_value,
          *pk_values
        ).affected_count
        if num_changed > 0
          @row[ colname ] = new_value
        end
      end

    end
  end
end