class Sequel::Schema::Generator
the “Migrations and Schema Modification” guide.
For more information on Sequel’s support for schema modification, see
the column method, which makes for a nicer DSL.
allowing users to specify column type as a method instead of using
Schema::Generator has some methods but also includes method_missing,
create a table.
gives the Database a table description, which the database uses to
object and a block of column/index/constraint specifications, and
It is used to specify table creation parameters. It takes a Database
to instantiate directly. Instances are created by Database#create_table.
Schema::Generator is an internal class that the user is not expected
def self.add_type_method(*types)
be constants/classes or a capitalized string/symbol with the same name
with that type as a constant. Types given should either already
Add a method for each of the given types that creates a column
def self.add_type_method(*types) types.each do |type| class_eval("def #{type}(name, opts={}); column(name, #{type}, opts); end", __FILE__, __LINE__) end end
def check(*args, &block)
check(:num=>1..5) # CHECK num >= 1 AND num <= 5
or args:
Add an unnamed constraint to the DDL, specified by the given block
def check(*args, &block) constraint(nil, *args, &block) end
def column(name, type, opts = {})
:unsigned :: Make the column type unsigned, only useful for integer
creating a unique index on the column.
:unique :: Mark the column as unique, generally has the same effect as
precision, respectively, of decimal columns.
An array of two integers can be provided to set the size and the
columns to specify the maximum number of characters the column will hold.
:size :: The size of the column, generally used with string
be used if you have a single, nonautoincrementing primary key column.
:primary_key :: Make the column as a single primary key column. This should only
(:restrict, cascade, :set_null, :set_default, :no_action).
:on_update :: Specify the behavior of this column when being updated
(:restrict, cascade, :set_null, :set_default, :no_action).
:on_delete :: Specify the behavior of this column when being deleted
to whatever the database default is.
or not allowing NULL values (if false). If unspecified, will default
:null :: Mark the column as allowing NULL values (if true),
using MySQL.
references the primary key of the associated table, except if you are
that this column references. Unnecessary if this column
:key :: For foreign key columns, the column in the associated table
:index :: Create an index on this column.
DEFERRABLE INITIALLY DEFERRED on key creation.
exists(yet) on referenced table. Basically it adds
reference table will use for its foreign key a value that does not
:deferrable :: This ensure Referential Integrity will work even if
:default :: The default value for the column.
The following options are supported:
integer :number
column :number, :integer
equivalent:
You can also create columns via method missing, so the following are
# ip inet
inet :ip
# name varchar(255) NOT NULL DEFAULT 'a'
column :name, String, :null=>false, :default=>'a'
# num INTEGER
column :num, :integer
Add a column with the given name, type, and opts to the DDL.
def column(name, type, opts = {}) columns << {:name => name, :type => type}.merge(opts) index(name) if opts[:index] end
def composite_foreign_key(columns, opts)
def composite_foreign_key(columns, opts) constraints << {:type => :foreign_key, :columns => columns}.merge(opts) end
def composite_primary_key(columns, *args)
def composite_primary_key(columns, *args) opts = args.pop || {} constraints << {:type => :primary_key, :columns => columns}.merge(opts) end
def constraint(name, *args, &block)
constraint(:blah, :num=>1..5) # CONSTRAINT blah CHECK num >= 1 AND num <= 5
with the given block or args.
Adds a named constraint (or unnamed if name is nil) to the DDL,
def constraint(name, *args, &block) constraints << {:name => name, :type => :check, :check => block || args} end
def dump_columns
Dump this generator's columns to a string that could be evaled inside
def dump_columns strings = [] cols = columns.dup if pkn = primary_key_name cols.delete_if{|x| x[:name] == pkn} pk = @primary_key.dup pkname = pk.delete(:name) @db.serial_primary_key_options.each{|k,v| pk.delete(k) if v == pk[k]} strings << "primary_key #{pkname.inspect}#{opts_inspect(pk)}" end cols.each do |c| c = c.dup name = c.delete(:name) type = c.delete(:type) opts = opts_inspect(c) strings << if type.is_a?(Class) "#{type.name} #{name.inspect}#{opts}" else "column #{name.inspect}, #{type.inspect}#{opts}" end end strings.join("\n") end
def dump_constraints
Dump this generator's constraints to a string that could be evaled inside
def dump_constraints cs = constraints.map do |c| c = c.dup type = c.delete(:type) case type when :check raise(Error, "can't dump check/constraint specified with Proc") if c[:check].is_a?(Proc) name = c.delete(:name) if !name and c[:check].length == 1 and c[:check].first.is_a?(Hash) "check #{c[:check].first.inspect[1...-1]}" else "#{name ? "constraint #{name.inspect}," : 'check'} #{c[:check].map{|x| x.inspect}.join(', ')}" end else cols = c.delete(:columns) "#{type} #{cols.inspect}#{opts_inspect(c)}" end end cs.join("\n") end
def dump_indexes(options={})
* :drop_index - Same as add_index, but create drop_index statements.
The value of this option should be the table name to use.
can be called outside of a generator but inside a migration.
* :add_index - Use add_index instead of index, so the methods
another instance to represent the same indexes. Options:
Dump this generator's indexes to a string that could be evaled inside
def dump_indexes(options={}) is = indexes.map do |c| c = c.dup cols = c.delete(:columns) if table = options[:add_index] || options[:drop_index] "#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{', :ignore_errors=>true' if options[:ignore_errors]}#{opts_inspect(c)}" else "index #{cols.inspect}#{opts_inspect(c)}" end end is.join("\n") end
def foreign_key(name, table=nil, opts = {})
foreign_key([:artist_name, :artist_location], :artists, :name=>:artist_fk)
you can provide the :name option to name the constraint:
composite foreign key), you can provide an array of columns as the first argument, and
If you want a foreign key constraint without adding a column (usually because it is a
foreign_key(:artist_id, :artists, :key=>:id) # artist_id INTEGER REFERENCES artists(id)
foreign_key(:artist_id, :artists) # artist_id INTEGER REFERENCES artists
foreign_key(:artist_id) # artist_id INTEGER
for available options.
Add a foreign key in the table that references another table to the DDL. See column
def foreign_key(name, table=nil, opts = {}) opts = case table when Hash table.merge(opts) when Symbol opts.merge(:table=>table) when NilClass opts else raise(Error, "The second argument to foreign_key should be a Hash, Symbol, or nil") end return composite_foreign_key(name, opts) if name.is_a?(Array) column(name, Integer, opts) end
def full_text_index(columns, opts = {})
def full_text_index(columns, opts = {}) index(columns, opts.merge(:type => :full_text)) end
def has_column?(name)
def has_column?(name) columns.any?{|c| c[:name] == name} end
def index(columns, opts = {})
index [:artist_id, :name]
# CREATE INDEX table_name_index ON table (name)
index :name
:where :: Create a partial index (only supported by some databases)
:unique :: Make the index unique, so duplicate values are not allowed.
:type :: The type of index to use (only supported by some databases)
The available options are:
Add an index on the given column(s) with the given options to the DDL.
def index(columns, opts = {}) indexes << {:columns => Array(columns)}.merge(opts) end
def initialize(db, &block)
Set the database in which to create the table, and evaluate the block
def initialize(db, &block) @db = db @columns = [] @indexes = [] @constraints = [] @primary_key = nil instance_eval(&block) if block @columns.unshift(@primary_key) if @primary_key && !has_column?(primary_key_name) end
def method_missing(type, name = nil, opts = {})
Add a column with the given type, name, and opts to the DDL. See +column+ for available
def method_missing(type, name = nil, opts = {}) name ? column(name, type, opts) : super end
def opts_inspect(opts)
def opts_inspect(opts) if opts[:default] opts = opts.dup de = case d = opts.delete(:default) when BigDecimal, Sequel::SQL::Blob "#{d.class.name}.new(#{d.to_s.inspect})" when DateTime, Date "#{d.class.name}.parse(#{d.to_s.inspect})" when Time "#{d.class.name}.parse(#{d.strftime('%H:%M:%S').inspect})" else d.inspect end ", :default=>#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}" else ", #{opts.inspect[1...-1]}" if opts.length > 0 end end
def primary_key(name, *args)
primary_key(:id)
Examples:
with a :primary_key=>true option.
should not use this method. Instead, you should use the regular +column+ method
If you want to create a primary key column that is not autoincrementing, you
as the second argument.
column, a single symbol can be used. In both cases, an options hash can be used
specifying the primary key columns. To create an autoincrementing primary key
To create a constraint, the first argument should be an array of column symbols
Adds an autoincrementing primary key column or a primary key constraint to the DDL.
def primary_key(name, *args) return composite_primary_key(name, *args) if name.is_a?(Array) @primary_key = @db.serial_primary_key_options.merge({:name => name}) if opts = args.pop opts = {:type => opts} unless opts.is_a?(Hash) if type = args.pop opts.merge!(:type => type) end @primary_key.merge!(opts) end @primary_key end
def primary_key_name
def primary_key_name @primary_key[:name] if @primary_key end
def spatial_index(columns, opts = {})
def spatial_index(columns, opts = {}) index(columns, opts.merge(:type => :spatial)) end
def unique(columns, opts = {})
Add a unique constraint on the given columns to the DDL.
def unique(columns, opts = {}) constraints << {:type => :unique, :columns => Array(columns)}.merge(opts) end