Class: Miguel::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/miguel/schema.rb

Overview

Class for defining database schema.

Defined Under Namespace

Modules: Output Classes: Column, ForeignKey, Index, Table

Constant Summary collapse

ZERO_TIME =

String denoting zero time in MySQL.

'0000-00-00 00:00:00'.freeze
DEFAULT_TIME =

String denoting default time.

'2000-01-01 00:00:00'.freeze
LOCK =

Mutex protecting access to thread sensitive variables.

Mutex.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Schema

Create new schema.



420
421
422
423
424
425
426
# File 'lib/miguel/schema.rb', line 420

def initialize( opts = {} )
  @tables = {}
  @aliases = {}
  @defaults = {}
  @callbacks = {}
  @opts = self.class.default_options.merge(opts)
end

Instance Attribute Details

#optsObject (readonly)

Schema options.



417
418
419
# File 'lib/miguel/schema.rb', line 417

def opts
  @opts
end

Class Method Details

.default_optionsObject

Get default schema options.



637
638
639
# File 'lib/miguel/schema.rb', line 637

def default_options
  sync{ @opts || {} }
end

.default_options=(opts) ⇒ Object Also known as: set_default_options

Set default schema options.



642
643
644
# File 'lib/miguel/schema.rb', line 642

def default_options=( opts )
  sync{ @opts = opts.nil? ? nil : opts.dup }
end

.define(opts = {}, &block) ⇒ Object

Define schema with provided block.



648
649
650
651
652
# File 'lib/miguel/schema.rb', line 648

def define( opts = {}, &block )
  sync do
    set_schema( new( opts ).define( &block ) )
  end
end

.load(name, opts = {}) ⇒ Object

Load schema from given file.



655
656
657
658
659
660
661
662
663
664
# File 'lib/miguel/schema.rb', line 655

def load( name, opts = {} )
  name = File.expand_path( name )
  sync do
    get_schema do
      with_options( opts ) do
        Kernel.load( name )
      end
    end
  end
end

Instance Method Details

#add_join_table(id_left, table_left, id_right, table_right, name = nil, &block) ⇒ Object Also known as: join_table

Helper for creating join tables conveniently. It is equivalent to the following: add_table name do foreign_key id_left, table_left foreign_key id_right, table_right primary_key [id_left, id_right] unique [id_right, id_left] end In case a block is provided, it is used to further extend the table defined.



462
463
464
465
466
467
468
469
470
471
# File 'lib/miguel/schema.rb', line 462

def add_join_table( id_left, table_left, id_right, table_right, name = nil, &block )
  name ||= [ table_left, table_right ].sort.join( '_' )
  add_table name do
    foreign_key id_left, table_left
    foreign_key id_right, table_right
    primary_key [ id_left, id_right ]
    unique [ id_right, id_left ]
    instance_eval &block if block
  end
end

#add_table(name, &block) ⇒ Object Also known as: table

Add table with given name, optionally defined with provided block.



444
445
446
447
448
449
450
# File 'lib/miguel/schema.rb', line 444

def add_table( name, &block )
  name = name.to_sym
  fail( ArgumentError, "table #{name} is already defined" ) if @tables[ name ]
  @tables[ name ] = table = Table.new( self, name )
  table.define( &block ) if block
  table
end

#apply_defaults(table_name, name, *args) ⇒ Object

Apply default options to given +add_table+ block statement. See +set_defaults+ for detailed explanation.



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/miguel/schema.rb', line 592

def apply_defaults( table_name, name, *args )
  opts = {}
  opts.merge!( get_defaults( :global ) )

  if name[ -1 ] == '?'
    opts[ :null ] = true
    original_name = name
    name = name[ 0..-2 ].to_sym
  end

  opts.merge!( get_defaults( name ) )
  opts.merge!( get_defaults( original_name ) ) if original_name

  if callback = @callbacks[ name ]
    callback.call( opts, args, table_name )
  end

  opts.merge!( args.pop ) if args.last.is_a? Hash
  args << opts unless opts.empty?

  [ ( @aliases[ name ] || name ), *args ]
end

#clear_defaults(name) ⇒ Object

Clear defaults and aliases for given statement.



504
505
506
507
508
509
# File 'lib/miguel/schema.rb', line 504

def clear_defaults( name )
  @aliases.delete( name )
  @defaults.delete( name )
  @callbacks.delete( name )
  self
end

#define(&block) ⇒ Object

Define schema with the provided block.



624
625
626
627
628
629
# File 'lib/miguel/schema.rb', line 624

def define( &block )
  fail( ArgumentError, "missing schema block" ) unless block
  set_standard_defaults unless opts[ :use_defaults ] == false
  instance_eval &block
  self
end

#dump(out = Dumper.new) ⇒ Object

Dump table definition to given output.



616
617
618
619
620
621
# File 'lib/miguel/schema.rb', line 616

def dump( out = Dumper.new )
  for table in tables
    table.dump( out )
  end
  out
end

#get_defaults(name) ⇒ Object

Get default options for given statement.



512
513
514
# File 'lib/miguel/schema.rb', line 512

def get_defaults( name )
  @defaults[ name ] || {}
end

#named_tables(names) ⇒ Object

Get tables with given names.



439
440
441
# File 'lib/miguel/schema.rb', line 439

def named_tables( names )
  @tables.values_at( *names )
end

#set_defaults(name, *args, &block) ⇒ Object

Set default options for given statement used in +add_table+ blocks. It uses the following arguments: +name+:: The name of the statement, like +:primary_key+ or +:String+. The special name +:global+ may be used to set default options for any statement. +alias+:: Optional real statement to use instead of +name+, like +:String+ instead of +:Text+. +args+:: Hash containing the default options for +name+. +block+:: Optional block which may further modify the options.

If a block is provided, it is invoked with the following arguments: +opts+:: The trailing options passed to given statement, to be modified as necessary. +args+:: Any leading arguments passed to given statement, as readonly context. +table+:: The name of the currently defined table, as readonly context.

The final options for each statement are created in the following order: +:global+ options, extended with +:null+ set to +true+ in case of ? syntax, merged with options for +name+ (without ?), modified by the optional +block+ callback, and merged with the original options used with the statement.

Also note that the defaults are applied in the instant the +table+ block is evaluated, so it is eventually possible (though not necessarily recommended) to change them in between.



494
495
496
497
498
499
500
501
# File 'lib/miguel/schema.rb', line 494

def set_defaults( name, *args, &block )
  clear_defaults( name )
  @aliases[ name ] = args.shift if args.first.is_a? Symbol
  @defaults[ name ] = args.pop if args.last.is_a? Hash
  @callbacks[ name ] = block
  fail( ArgumentError, "invalid defaults for #{name}" ) unless args.empty?
  self
end

#set_standard_defaults(opts = self.opts) ⇒ Object

Set standard defaults and aliases for often used types.

The current set of defaults is as follows:

:global, :null => false

:Bool, :TrueClass :True, :TrueClass, :default => true :False, :TrueClass, :default => false :Signed, :integer, :unsigned => false :Unsigned, :integer, :unsigned => true :Text, :String, :text => true :Time, :timestamp, :default => '0000-00-00 00:00:00' :Time?, :timestamp, :default => nil

:unique, :index, :unique => true

:Key, :integer, :unsigned => false :primary_key, :type => :integer, :unsigned => false :foreign_key, :key => :id, :type => :integer, :unsigned => false

If the +unsigned_keys+ option is set to true, the keys are set up to use unsigned integers instead.



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
# File 'lib/miguel/schema.rb', line 539

def set_standard_defaults( opts = self.opts )

  # We set NOT NULL on everything by default, but note the ?
  # syntax (like Text?) which declares the column as NULL.

  set_defaults :global, :null => false

  # We also like our keys unsigned, so we allow setting that, too.
  # Unfortunately, :unsigned currently works only with :integer,
  # not the default :Integer, and :integer can't be specified for compound keys,
  # so we have to use the callback to set the type only at correct times.
  # Furthermore, Postgres's autoincrementing serials only work with Integer,
  # so we set the type only as long as the unsigned keys are requested.

  unsigned_keys = !! opts[ :unsigned_keys ]

  set_defaults :Key, :integer, :unsigned => unsigned_keys
  set_defaults :primary_key, :unsigned => unsigned_keys do |opts,args,table|
    opts[ :type ] ||= :integer unless args.first.is_a? Array or not opts[ :unsigned ]
  end
  set_defaults :foreign_key, :key => :id, :unsigned => unsigned_keys do |opts,args,table|
    opts[ :type ] ||= :integer unless args.first.is_a? Array or not opts[ :unsigned ]
  end

  # Save some typing for unique indexes.

  set_defaults :unique, :index, :unique => true

  # Type shortcuts we use frequently.

  set_defaults :Bool, :TrueClass
  set_defaults :True, :TrueClass, :default => true
  set_defaults :False, :TrueClass, :default => false

  set_defaults :Signed, :integer, :unsigned => false
  set_defaults :Unsigned, :integer, :unsigned => ! opts[ :signed_unsigned ]

  set_defaults :String, :text => false
  set_defaults :Text, :String, :text => true

  # We want times to be stored as 4 byte timestamps, however
  # we have to be careful to turn off the MySQL autoupdate behavior.
  # That's why we have to set defaults explicitly.

  default_time = opts[ :mysql_timestamps ] ? ZERO_TIME : DEFAULT_TIME
  set_defaults :Time, :timestamp, :default => default_time
  set_defaults :Time?, :timestamp, :default => nil

  self
end

#table_namesObject

Get names of all tables.



434
435
436
# File 'lib/miguel/schema.rb', line 434

def table_names
  @tables.keys
end

#tablesObject

Get all tables.



429
430
431
# File 'lib/miguel/schema.rb', line 429

def tables
  @tables.values
end