module ActiveRecord::ConnectionAdapters::SchemaStatements
def add_check_constraint(table_name, expression, if_not_exists: false, **options)
[:validate]
Silently ignore if the constraint already exists, rather than raise an error.
[:if_not_exists]
The constraint name. Defaults to chk_rails_
[:name]
The +options+ hash can include the following keys:
ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
generates:
add_check_constraint :products, "price > 0", name: "price_check"
representation of verifiable boolean condition.
Adds a new check constraint to the table. +expression+ is a String
def add_check_constraint(table_name, expression, if_not_exists: false, **options) return unless supports_check_constraints? options = check_constraint_options(table_name, expression, options) return if if_not_exists && check_constraint_exists?(table_name, **options) at = create_alter_table(table_name) at.add_check_constraint(expression, options) execute schema_creation.accept(at) end
def add_column(table_name, column_name, type, **options)
# Ignores the method call if the column exists
# ALTER TABLE "shapes" ADD "triangle" polygon
add_column(:shapes, :triangle, 'polygon')
# Defines a column with a database-specific type.
# ALTER TABLE "users" ADD "skills" text[]
add_column(:users, :skills, :text, array: true)
# Defines a column that stores an array of a type.
# ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
add_column(:measurements, :huge_integer, :decimal, precision: 30)
# probably wouldn't hurt to include it.
# While :scale defaults to zero on most databases, it
# ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
# ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
# ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
# ALTER TABLE "users" ADD "picture" blob(2097152)
add_column(:users, :picture, :binary, limit: 2.megabytes)
== Examples
Default (38,0).
* SqlServer: :precision [1..38], :scale [0..38].
Default is (38,0).
* Oracle: :precision [1..38], :scale [-84..127].
but the maximum supported :precision is 16. No default.
* SQLite3: No restrictions on :precision and :scale,
:scale [0..infinity]. No default.
* PostgreSQL: :precision [1..infinity],
Default is (10,0).
* MySQL: :precision [1..65], :scale [0..30].
:precision.
:precision, and makes no comments about the requirements of
* The SQL standard says the default scale should be 0, :scale <=
:decimal columns:
Please be aware of different RDBMS implementations behavior with
range from -999.99 to 999.99.
and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
the decimal point. For example, the number 123.45 has a precision of 5
and the scale is the number of digits that can be stored following
Note: The precision is the total number of significant digits,
duplicate column errors.
Specifies if the column already exists to not try to re-add it. This will avoid
* :if_not_exists -
Specifies the scale for the :decimal and :numeric columns.
* :scale -
:datetime, and :time columns.
Specifies the precision for the :decimal, :numeric,
* :precision -
Allows or disallows +NULL+ values in the column.
* :null -
This option is ignored by some backends.
and number of bytes for :text, :binary, :blob, and :integer columns.
Requests a maximum column length. This is the number of characters for a :string column
* :limit -
The column's default value. Use +nil+ for +NULL+.
* :default -
If not specified, the column will have the same collation as the table.
Specifies the collation for a :string or :text column.
* :collation -
Specifies the comment for the column. This option is ignored by some backends.
* :comment -
Available options are (none of these exists by default):
agnostic and should usually be avoided.
database (for example, "polygon" in MySQL), but this will not be database
You may use a type not in this list as long as it is supported by your
:binary, :blob, :boolean.
:datetime, :time, :date,
:integer, :bigint, :float, :decimal, :numeric,
:primary_key, :string, :text,
which is one of the following:
The +type+ parameter is normally one of the migrations native types,
See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
Add a new +type+ column named +column_name+ to +table_name+.
def add_column(table_name, column_name, type, **options) add_column_def = build_add_column_definition(table_name, column_name, type, **options) return unless add_column_def execute schema_creation.accept(add_column_def) end
def add_column_for_alter(table_name, column_name, type, **options)
def add_column_for_alter(table_name, column_name, type, **options) td = create_table_definition(table_name) cd = td.new_column_definition(column_name, type, **options) schema_creation.accept(AddColumnDefinition.new(cd)) end
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
def add_columns(table_name, *column_names, type:, **options) # :nodoc: column_names.each do |column_name| add_column(table_name, column_name, type, **options) end end
def add_foreign_key(from_table, to_table, **options)
(PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or
[:deferrable]
(PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
[:validate]
duplicate column errors.
Specifies if the foreign key already exists to not try to re-add it. This will avoid
[:if_not_exists]
Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade+, and +:restrict+
[:on_update]
Action that happens ON DELETE. Valid values are +:nullify+, +:cascade+, and +:restrict+
[:on_delete]
The constraint name. Defaults to fk_rails_
[:name]
Pass an array to create a composite foreign key.
The primary key column name on +to_table+. Defaults to +id+.
[:primary_key]
Pass an array to create a composite foreign key.
The foreign key column name on +from_table+. Defaults to to_table.singularize + "_id".
[:column]
The +options+ hash can include the following keys:
ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
generates:
add_foreign_key :articles, :authors, on_delete: :cascade
====== Creating a cascading foreign key
ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id")
generates:
add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id]
Assuming "carts" table has "(shop_id, user_id)" as a primary key.
====== Creating a composite foreign key
ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
generates:
add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
====== Creating a foreign key on a specific column
add_foreign_key(:articles, :authors, if_not_exists: true)
====== Creating a foreign key, ignoring method call if the foreign key exists
ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
generates:
add_foreign_key :articles, :authors
====== Creating a simple foreign key
+from_table+ and +column+. A custom name can be specified with the :name option.
+identifier+ is a 10 character long string which is deterministically generated from the
The foreign key will be named after the following pattern: fk_rails_
+to_table+ contains the referenced primary key.
Adds a new foreign key. +from_table+ is the table with the key column,
def add_foreign_key(from_table, to_table, **options) return unless use_foreign_keys? return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column)) options = foreign_key_options(from_table, to_table, options) at = create_alter_table from_table at.add_foreign_key to_table, options execute schema_creation.accept(at) end
def add_index(table_name, column_name, **options)
Concurrently adding an index is not supported in a transaction.
Note: only supported by PostgreSQL.
# CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
add_index(:developers, :name, algorithm: :concurrently)
====== Creating an index with a specific algorithm
Note: only supported by MySQL.
CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
generates:
add_index(:developers, :name, type: :fulltext)
====== Creating an index with a specific type
Note: only supported by PostgreSQL
# CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
# CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
# CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
====== Creating an index with a specific operator class
Note: only supported by PostgreSQL and MySQL
CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
generates:
add_index(:developers, :name, using: 'btree')
====== Creating an index with a specific method
Note: only supported by PostgreSQL.
CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id)
generates:
add_index(:accounts, :branch_id, include: :party_id)
====== Creating an index that includes additional columns
Note: Partial indexes are only supported for PostgreSQL and SQLite.
CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
generates:
add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
====== Creating a partial index
Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
generates:
add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
====== Creating an index with a sort order (desc or asc, asc is the default)
Note: only supported by MySQL
CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
generates:
add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
====== Creating an index with specific key lengths for multiple keys
CREATE INDEX by_name ON accounts(name(10))
generates:
add_index(:accounts, :name, name: 'by_name', length: 10)
====== Creating an index with specific key length
CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
generates:
add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
====== Creating a named index
CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
generates:
add_index(:accounts, [:branch_id, :party_id], unique: true)
====== Creating a unique index
Note: Not supported by MySQL.
CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
generates:
add_index(:suppliers, :name, if_not_exists: true)
====== Creating a index which already exists
CREATE INDEX index_suppliers_on_name ON suppliers(name)
generates:
add_index(:suppliers, :name)
====== Creating a simple index
you pass :name as an option.
The index will be named after the table and the column name(s), unless
an Array of Symbols.
Adds a new index to the table. +column_name+ can be a single Symbol, or
def add_index(table_name, column_name, **options) create_index = build_create_index_definition(table_name, column_name, **options) execute schema_creation.accept(create_index) end
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc: options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct) column_names = index_column_names(column_name) index_name = name&.to_s index_name ||= index_name(table_name, column_names) validate_index_length!(table_name, index_name, internal) index = IndexDefinition.new( table_name, index_name, options[:unique], column_names, lengths: options[:length] || {}, orders: options[:order] || {}, opclasses: options[:opclass] || {}, where: options[:where], type: options[:type], using: options[:using], include: options[:include], nulls_not_distinct: options[:nulls_not_distinct], comment: options[:comment] ) [index, index_algorithm(options[:algorithm]), if_not_exists] end
def add_index_sort_order(quoted_columns, **options)
def add_index_sort_order(quoted_columns, **options) orders = options_for_index_columns(options[:order]) quoted_columns.each do |name, column| column << " #{orders[name].upcase}" if orders[name].present? end end
def add_options_for_index_columns(quoted_columns, **options)
Overridden by the MySQL adapter for supporting index lengths and by
def add_options_for_index_columns(quoted_columns, **options) if supports_index_sort_order? quoted_columns = add_index_sort_order(quoted_columns, **options) end quoted_columns end
def add_reference(table_name, ref_name, **options)
add_reference(:products, :supplier, foreign_key: { to_table: :firms })
====== Create a supplier_id column and a foreign key to the firms table
add_reference(:products, :supplier, foreign_key: true)
====== Create a supplier_id column and appropriate foreign key
add_reference(:products, :supplier, index: { name: "my_supplier_index" })
====== Create a supplier_id column with a named index
add_reference(:products, :supplier, index: { unique: true })
====== Create a supplier_id column with a unique index
add_reference(:products, :supplier, polymorphic: true)
====== Create supplier_id, supplier_type columns
add_reference(:products, :user, type: :string)
====== Create a user_id string column
add_reference(:products, :user, index: false)
====== Create a user_id bigint column without an index
Whether the column allows nulls. Defaults to true.
[:null]
Whether an additional +_type+ column should be added. Defaults to false.
[:polymorphic]
pass :to_table with the appropriate table name.
to add. In case the join table can't be inferred from the association
Add an appropriate foreign key constraint. Defaults to false, pass true
[:foreign_key]
See #add_index for usage of this option.
Add an appropriate index. Defaults to true.
[:index]
The reference column type. Defaults to +:bigint+.
[:type]
The +options+ hash can include the following keys:
Optionally adds a +_type+ column, if :polymorphic option is provided.
the :type option can be used to specify a different type.
Adds a reference. The reference column is a bigint by default,
def add_reference(table_name, ref_name, **options) ReferenceDefinition.new(ref_name, **options).add(table_name, self) end
def add_timestamps(table_name, **options)
add_timestamps(:suppliers, null: true)
Additional options (like +:null+) are forwarded to #add_column.
Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
def add_timestamps(table_name, **options) fragments = add_timestamps_for_alter(table_name, **options) execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}" end
def add_timestamps_for_alter(table_name, **options)
def add_timestamps_for_alter(table_name, **options) options[:null] = false if options[:null].nil? if !options.key?(:precision) && supports_datetime_with_precision? options[:precision] = 6 end [ add_column_for_alter(table_name, :created_at, :datetime, **options), add_column_for_alter(table_name, :updated_at, :datetime, **options) ] end
def assume_migrated_upto_version(version)
def assume_migrated_upto_version(version) version = version.to_i sm_table = quote_table_name(schema_migration.table_name) migrated = migration_context.get_all_versions versions = migration_context.migrations.map(&:version) unless migrated.include?(version) execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})" end inserting = (versions - migrated).select { |v| v < version } if inserting.any? if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end execute insert_versions_sql(inserting) end end
def build_add_column_definition(table_name, column_name, type, **options) # :nodoc:
passing a +table_name+, +column_name+, +type+ and other options that can be passed.
if the same arguments were passed to #add_column. See #add_column for information about
This definition object contains information about the column that would be created
Builds an AlterTable object for adding a column to a table.
def build_add_column_definition(table_name, column_name, type, **options) # :nodoc: return if options[:if_not_exists] == true && column_exists?(table_name, column_name) if supports_datetime_with_precision? if type == :datetime && !options.key?(:precision) options[:precision] = 6 end end alter_table = create_alter_table(table_name) alter_table.add_column(column_name, type, **options) alter_table end
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
information about passing a +table_name+, +column_name+, +type+ and other options that can be passed.
if the same arguments were passed to #change_column_default. See #change_column_default for
This definition object contains information about the column change that would occur
Builds a ChangeColumnDefaultDefinition object.
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc: raise NotImplementedError, "build_change_column_default_definition is not implemented" end
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
passing a +table_name+, +column_name+, and other additional options that can be passed.
if the same arguments were passed to #add_index. See #add_index for information about
This definition object contains information about the index that would be created
Builds a CreateIndexDefinition object.
def build_create_index_definition(table_name, column_name, **options) # :nodoc: index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) CreateIndexDefinition.new(index, algorithm, if_not_exists) end
def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc:
information about what arguments should be passed.
if the same arguments were passed to #create_join_table. See #create_join_table for
This definition object contains information about the table that would be created
Builds a TableDefinition object for a join table.
def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc: join_table_name = find_join_table_name(table_1, table_2, options) column_options.reverse_merge!(null: false, index: false) t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) } build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td| td.references t1_ref, **column_options td.references t2_ref, **column_options yield td if block_given? end end
def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
if the same arguments were passed to #create_table. See #create_table for information about
Returns a TableDefinition object containing information about the table that would be created
def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options) table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options)) table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options)) yield table_definition if block_given? table_definition end
def bulk_change_table(table_name, operations) # :nodoc:
def bulk_change_table(table_name, operations) # :nodoc: sql_fragments = [] non_combinable_operations = [] operations.each do |command, args| table, arguments = args.shift, args method = :"#{command}_for_alter" if respond_to?(method, true) sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) } sql_fragments.concat(sqls) non_combinable_operations.concat(procs) else execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? non_combinable_operations.each(&:call) sql_fragments = [] non_combinable_operations = [] send(command, table, *arguments) end end execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? non_combinable_operations.each(&:call) end
def can_remove_index_by_name?(column_name, options)
def can_remove_index_by_name?(column_name, options) column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty? end
def change_column(table_name, column_name, type, **options)
change_column(:accounts, :description, :text)
change_column(:suppliers, :name, :string, limit: 80)
See TableDefinition#column for details of the options you can use.
Changes the column's definition according to the new options.
def change_column(table_name, column_name, type, **options) raise NotImplementedError, "change_column is not implemented" end
def change_column_comment(table_name, column_name, comment_or_changes)
reversible in migration:
Passing a hash containing +:from+ and +:to+ will make this change
Changes the comment for a column or removes it if +nil+.
def change_column_comment(table_name, column_name, comment_or_changes) raise NotImplementedError, "#{self.class} does not support changing column comments" end
def change_column_default(table_name, column_name, default_or_changes)
change_column_default(:posts, :state, from: nil, to: "draft")
reversible in migration:
Passing a hash containing +:from+ and +:to+ will make this change
change_column_default(:users, :email, nil)
Setting the default to +nil+ effectively drops the default:
change_column_default(:accounts, :authorized, 1)
change_column_default(:suppliers, :qualification, 'new')
Sets a new default value for a column:
def change_column_default(table_name, column_name, default_or_changes) raise NotImplementedError, "change_column_default is not implemented" end
def change_column_default_for_alter(table_name, column_name, default_or_changes)
def change_column_default_for_alter(table_name, column_name, default_or_changes) cd = build_change_column_default_definition(table_name, column_name, default_or_changes) schema_creation.accept(cd) end
def change_column_null(table_name, column_name, null, default = nil)
constraint if needed, since otherwise those rows would not be valid.
NULLs with some other value. Use that one when enabling the
The method accepts an optional fourth argument to replace existing
allows them to be +NULL+ (drops the constraint).
change_column_null(:users, :nickname, true)
says nicknames cannot be +NULL+ (adds the constraint), whereas
change_column_null(:users, :nickname, false)
indicates whether the value can be +NULL+. For example
Sets or removes a NOT NULL constraint on a column. The +null+ flag
def change_column_null(table_name, column_name, null, default = nil) raise NotImplementedError, "change_column_null is not implemented" end
def change_table(table_name, base = self, **options)
end
t.remove_index :company_id
change_table(:suppliers) do |t|
====== Remove an index
end
t.remove :width, :height
t.remove :company_id
change_table(:suppliers) do |t|
====== Remove several columns
end
t.remove :company
change_table(:suppliers) do |t|
====== Remove a column
Creates company_type(varchar) and company_id(bigint) columns.
end
t.belongs_to :company, polymorphic: true
change_table(:suppliers) do |t|
====== Add a polymorphic foreign key column
Creates a company_id(bigint) column.
end
t.references :company
change_table(:suppliers) do |t|
====== Add a foreign key column
end
t.timestamps
change_table(:suppliers) do |t|
====== Add created_at/updated_at columns
end
t.integer :width, :height, null: false, default: 0
change_table(:suppliers) do |t|
====== Add 2 integer columns
end
t.change :metadata, :json
change_table(:suppliers) do |t|
====== Change type of a column
end
t.column :name, :string, limit: 60
change_table(:suppliers) do |t|
====== Add a column
Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
Defaults to false.
ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
Set this to true to make this a bulk alter query, such as
[:bulk]
The +options+ hash can include the following keys:
end
# Other column alterations here
t.column :name, :string, limit: 60
change_table(:suppliers) do |t|
# change_table() yields a Table instance
A block for changing columns in +table+.
def change_table(table_name, base = self, **options) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) yield update_table_definition(table_name, recorder) bulk_change_table(table_name, recorder.commands) else yield update_table_definition(table_name, base) end end
def change_table_comment(table_name, comment_or_changes)
reversible in migration:
Passing a hash containing +:from+ and +:to+ will make this change
Changes the comment for a table or removes it if +nil+.
def change_table_comment(table_name, comment_or_changes) raise NotImplementedError, "#{self.class} does not support changing table comments" end
def check_constraint_exists?(table_name, **options)
check_constraint_exists?(:products, name: "price_check")
Checks to see if a check constraint exists on a table for a given check constraint definition.
def check_constraint_exists?(table_name, **options) if !options.key?(:name) && !options.key?(:expression) raise ArgumentError, "At least one of :name or :expression must be supplied" end check_constraint_for(table_name, **options).present? end
def check_constraint_for(table_name, **options)
def check_constraint_for(table_name, **options) return unless supports_check_constraints? chk_name = check_constraint_name(table_name, **options) check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) } end
def check_constraint_for!(table_name, expression: nil, **options)
def check_constraint_for!(table_name, expression: nil, **options) check_constraint_for(table_name, expression: expression, **options) || raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}") end
def check_constraint_name(table_name, **options)
def check_constraint_name(table_name, **options) options.fetch(:name) do expression = options.fetch(:expression) identifier = "#{table_name}_#{expression}_chk" hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10) "chk_rails_#{hashed_identifier}" end end
def check_constraint_options(table_name, expression, options) # :nodoc:
def check_constraint_options(table_name, expression, options) # :nodoc: options = options.dup options[:name] ||= check_constraint_name(table_name, expression: expression, **options) options end
def check_constraints(table_name)
Returns an array of check constraints for the given table.
def check_constraints(table_name) raise NotImplementedError end
def column_exists?(table_name, column_name, type = nil, **options)
column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
column_exists?(:suppliers, :name, :string, null: false)
column_exists?(:suppliers, :name, :string, default: 'default')
column_exists?(:suppliers, :name, :string, limit: 100)
# Check a column exists with a specific definition
column_exists?(:suppliers, :name, :string)
# for types that may get cast to something else (eg. char, bigint).
# This works for standard non-casted types (eg. string) but is unreliable
#
# Check a column exists of a particular type
column_exists?(:suppliers, :name)
# Check a column exists
Checks to see if a column exists in a given table.
def column_exists?(table_name, column_name, type = nil, **options) column_name = column_name.to_s checks = [] checks << lambda { |c| c.name == column_name } checks << lambda { |c| c.type == type.to_sym rescue nil } if type column_options_keys.each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end columns(table_name).any? { |c| checks.all? { |check| check[c] } } end
def column_options_keys
def column_options_keys [:limit, :precision, :scale, :default, :null, :collation, :comment] end
def columns(table_name)
def columns(table_name) table_name = table_name.to_s definitions = column_definitions(table_name) definitions.map do |field| new_column_from_field(table_name, field, definitions) end end
def columns_for_distinct(columns, orders) # :nodoc:
columns_for_distinct("posts.id", ["posts.created_at desc"])
require the order columns appear in the SELECT.
PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
def columns_for_distinct(columns, orders) # :nodoc: columns end
def create_alter_table(name)
def create_alter_table(name) AlterTable.new create_table_definition(name) end
def create_join_table(table_1, table_2, column_options: {}, **options)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
part_id bigint NOT NULL,
assembly_id bigint NOT NULL,
CREATE TABLE assemblies_parts (
generates:
create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
====== Add a backend specific option to the generated SQL (MySQL)
end
t.index :category_id
t.index :product_id
create_join_table :products, :categories do |t|
its block form to do so yourself:
Note that #create_join_table does not create any indices by default; you can use
Defaults to false.
Set to true to drop the table before creating it.
[:force]
Make a temporary table.
[:temporary]
Any extra options you want appended to the table definition.
[:options]
Any extra options you want appended to the columns definition.
[:column_options]
Sets the table name, overriding the default.
[:table_name]
You can pass an +options+ hash which can include the following keys:
create_join_table(:assemblies, :parts)
# Creates a table called 'assemblies_parts' with no id.
arguments. These arguments can be a String or a Symbol.
Creates a new join table with the name created using the lexical order of the first two
def create_join_table(table_1, table_2, column_options: {}, **options) join_table_name = find_join_table_name(table_1, table_2, options) column_options.reverse_merge!(null: false, index: false) t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) } create_table(join_table_name, **options.merge!(id: false)) do |td| td.references t1_ref, **column_options td.references t2_ref, **column_options yield td if block_given? end end
def create_schema_dumper(options) # :nodoc:
def create_schema_dumper(options) # :nodoc: SchemaDumper.create(self, options) end
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
CREATE TEMPORARY TABLE long_query AS
generates:
as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
create_table(:long_query, temporary: true,
====== Create a temporary table based on a query
)
supplier_id bigint
category_id bigint,
CREATE TABLE categories_suppliers (
generates:
end
t.column :supplier_id, :bigint
t.column :category_id, :bigint
create_table(:categories_suppliers, id: false) do |t|
====== Do not add a primary key column
ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
ALTER TABLE ONLY "orders"
);
client_id bigint NOT NULL
product_id bigint NOT NULL,
CREATE TABLE orders (
generates:
end
t.belongs_to :client
t.belongs_to :product
create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
====== Create a composite primary key
)
label varchar
id varchar PRIMARY KEY,
CREATE TABLE tags (
generates:
end
t.column :label, :string
create_table(:tags, id: :string) do |t|
====== Change the primary key column type
)
name varchar(80)
guid bigint auto_increment PRIMARY KEY,
CREATE TABLE objects (
generates:
end
t.column :name, :string, limit: 80
create_table(:objects, primary_key: 'guid') do |t|
====== Rename the primary key column
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
id bigint auto_increment PRIMARY KEY
CREATE TABLE suppliers (
generates:
create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
====== Add a backend specific option to the generated SQL (MySQL)
ignored, as are the :id and :primary_key options.
SQL to use to generate the table. When this option is used, the block is
[:as]
Defaults to false.
Set to true to avoid raising an error when the table already exists.
[:if_not_exists]
Defaults to false.
Set to +:cascade+ to drop dependent objects as well.
Set to true to drop the table before creating it.
[:force]
Make a temporary table.
[:temporary]
Any extra options you want appended to the table definition.
[:options]
to define the key explicitly.
{self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
primary key. This can be avoided by using
Note that Active Record models will automatically detect their
If an array is passed, a composite primary key will be created.
Defaults to +id+. If :id is false, then this option is ignored.
The name of the primary key, if one is to be added automatically.
[:primary_key]
A Symbol can be used to specify the type of the generated primary key column.
Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
Whether to automatically add a primary key column. Defaults to true.
[:id]
The +options+ hash can include the following keys:
add_column(:suppliers, :name, :string, {limit: 60})
# Add a column to 'suppliers'.
create_table(:suppliers)
# Creates a table called 'suppliers' with no columns.
=== Regular form
end
# Other fields here
t.string :name, limit: 60
create_table(:suppliers) do |t|
# You can also use the column types as method calls, rather than calling the column method.
=== Block form, with shorthand
end
# Other fields here
t.column :name, :string, limit: 60
create_table(:suppliers) do |t|
# table.
# This form will not only create the table, but also columns for the
# create_table() passes a TableDefinition object to the block.
=== Block form
form or the regular form, like this:
There are two ways to work with #create_table. You can use the block
be a String or a Symbol.
Creates a new table with the name +table_name+. +table_name+ may either
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block) validate_create_table_options!(options) validate_table_length!(table_name) unless options[:_uses_legacy_table_name] td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block) if force drop_table(table_name, force: force, if_exists: true) else schema_cache.clear_data_source_cache!(table_name.to_s) end result = execute schema_creation.accept(td) unless supports_indexes_in_create? td.indexes.each do |column_name, index_options| add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists) end end if supports_comments? && !supports_comments_in_create? if table_comment = td.comment.presence change_table_comment(table_name, table_comment) end td.columns.each do |column| change_column_comment(table_name, column.name, column.comment) if column.comment.present? end end result end
def create_table_definition(name, **options)
def create_table_definition(name, **options) TableDefinition.new(self, name, **options) end
def data_source_exists?(name)
data_source_exists?(:ebooks)
Checks to see if the data source +name+ exists on the database.
def data_source_exists?(name) query_values(data_source_sql(name), "SCHEMA").any? if name.present? rescue NotImplementedError data_sources.include?(name.to_s) end
def data_source_sql(name = nil, type: nil)
def data_source_sql(name = nil, type: nil) raise NotImplementedError end
def data_sources
Returns the relation names usable to back Active Record models.
def data_sources query_values(data_source_sql, "SCHEMA") rescue NotImplementedError tables | views end
def distinct_relation_for_primary_key(relation) # :nodoc:
def distinct_relation_for_primary_key(relation) # :nodoc: primary_key_columns = Array(relation.primary_key).map do |column| visitor.compile(relation.table[column]) end values = columns_for_distinct( primary_key_columns, relation.order_values ) limited = relation.reselect(values).distinct! limited_ids = select_rows(limited.arel, "SQL").map do |results| results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL end if limited_ids.empty? relation.none! else relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h) end relation.limit_value = relation.offset_value = nil relation end
def drop_join_table(table_1, table_2, **options)
to provide one in a migration's +change+ method so it can be reverted.
Although this command ignores the block if one is given, it can be helpful
See #create_join_table and #drop_table for details.
Drops the join table specified by the given arguments.
def drop_join_table(table_1, table_2, **options) join_table_name = find_join_table_name(table_1, table_2, options) drop_table(join_table_name, **options) end
def drop_table(table_name, **options)
it can be helpful to provide these in a migration's +change+ method so it can be reverted.
Although this command ignores most +options+ and the block if one is given,
Defaults to false.
Set to +true+ to only drop the table if it exists.
[:if_exists]
Defaults to false.
Set to +:cascade+ to drop dependent objects as well.
[:force]
Drops a table from the database.
def drop_table(table_name, **options) schema_cache.clear_data_source_cache!(table_name.to_s) execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}" end
def dump_schema_information # :nodoc:
def dump_schema_information # :nodoc: versions = schema_migration.versions insert_versions_sql(versions) if versions.any? end
def expression_column_name?(column_name)
def expression_column_name?(column_name) column_name.is_a?(String) && /\W/.match?(column_name) end
def extract_foreign_key_action(specifier)
def extract_foreign_key_action(specifier) case specifier when "CASCADE"; :cascade when "SET NULL"; :nullify when "RESTRICT"; :restrict end end
def extract_new_default_value(default_or_changes)
def extract_new_default_value(default_or_changes) if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) default_or_changes[:to] else default_or_changes end end
def fetch_type_metadata(sql_type)
def fetch_type_metadata(sql_type) cast_type = lookup_cast_type(sql_type) SqlTypeMetadata.new( sql_type: sql_type, type: cast_type.type, limit: cast_type.limit, precision: cast_type.precision, scale: cast_type.scale, ) end
def foreign_key_column_for(table_name, column_name) # :nodoc:
def foreign_key_column_for(table_name, column_name) # :nodoc: name = strip_table_name_prefix_and_suffix(table_name) "#{name.singularize}_#{column_name}" end
def foreign_key_exists?(from_table, to_table = nil, **options)
foreign_key_exists?(:accounts, name: "special_fk_name")
# Checks to see if a foreign key with a custom name exists.
foreign_key_exists?(:accounts, column: :owner_id)
# Checks to see if a foreign key on a specified column exists.
foreign_key_exists?(:accounts, :branches)
# Checks to see if a foreign key exists.
Checks to see if a foreign key exists on a table for a given foreign key definition.
def foreign_key_exists?(from_table, to_table = nil, **options) foreign_key_for(from_table, to_table: to_table, **options).present? end
def foreign_key_for(from_table, **options)
def foreign_key_for(from_table, **options) return unless use_foreign_keys? foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } end
def foreign_key_for!(from_table, to_table: nil, **options)
def foreign_key_for!(from_table, to_table: nil, **options) foreign_key_for(from_table, to_table: to_table, **options) || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}") end
def foreign_key_name(table_name, options)
def foreign_key_name(table_name, options) options.fetch(:name) do columns = Array(options.fetch(:column)).map(&:to_s) identifier = "#{table_name}_#{columns * '_and_'}_fk" hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10) "fk_rails_#{hashed_identifier}" end end
def foreign_key_options(from_table, to_table, options) # :nodoc:
def foreign_key_options(from_table, to_table, options) # :nodoc: options = options.dup if options[:primary_key].is_a?(Array) options[:column] ||= options[:primary_key].map do |pk_column| foreign_key_column_for(to_table, pk_column) end else options[:column] ||= foreign_key_column_for(to_table, "id") end options[:name] ||= foreign_key_name(from_table, options) if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array) if Array(options[:primary_key]).size != Array(options[:column]).size raise ArgumentError, <<~MSG.squish For composite primary keys, specify :column and :primary_key, where :column must reference all the :primary_key columns from #{to_table.inspect} MSG end end options end
def foreign_keys(table_name)
Returns an array of foreign keys for the given table.
def foreign_keys(table_name) raise NotImplementedError, "foreign_keys is not implemented" end
def foreign_keys_enabled?
def foreign_keys_enabled? @config.fetch(:foreign_keys, true) end
def generate_index_name(table_name, column)
def generate_index_name(table_name, column) name = "index_#{table_name}_on_#{Array(column) * '_and_'}" return name if name.bytesize <= max_index_name_size # Fallback to short version, add hash to ensure uniqueness hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10) name = "idx_on_#{Array(column) * '_'}" short_limit = max_index_name_size - hashed_identifier.bytesize short_name = name.mb_chars.limit(short_limit).to_s "#{short_name}#{hashed_identifier}" end
def index_algorithm(algorithm) # :nodoc:
def index_algorithm(algorithm) # :nodoc: index_algorithms.fetch(algorithm) do raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}" end if algorithm end
def index_column_names(column_names)
def index_column_names(column_names) if expression_column_name?(column_names) column_names else Array(column_names) end end
def index_exists?(table_name, column_name, **options)
index_exists?(:suppliers, :company_id, valid: true)
# Check a valid index exists (PostgreSQL only)
index_exists?(:suppliers, :company_id, name: "idx_company_id")
# Check an index with a custom name exists
index_exists?(:suppliers, :company_id, unique: true)
# Check a unique index exists
index_exists?(:suppliers, [:company_id, :company_type])
# Check an index on multiple columns exists
index_exists?(:suppliers, :company_id)
# Check an index exists
Checks to see if an index exists on a table for a given index definition.
def index_exists?(table_name, column_name, **options) indexes(table_name).any? { |i| i.defined_for?(column_name, **options) } end
def index_name(table_name, options) # :nodoc:
def index_name(table_name, options) # :nodoc: if Hash === options if options[:column] generate_index_name(table_name, options[:column]) elsif options[:name] options[:name] else raise ArgumentError, "You must specify the index name" end else index_name(table_name, index_name_options(options)) end end
def index_name_exists?(table_name, index_name)
def index_name_exists?(table_name, index_name) index_name = index_name.to_s indexes(table_name).detect { |i| i.name == index_name } end
def index_name_for_remove(table_name, column_name, options)
def index_name_for_remove(table_name, column_name, options) return options[:name] if can_remove_index_by_name?(column_name, options) checks = [] if !options.key?(:name) && expression_column_name?(column_name) options[:name] = index_name(table_name, column_name) column_names = [] else column_names = index_column_names(column_name || options[:column]) end checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name) if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names)) checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) } end raise ArgumentError, "No name or columns specified" if checks.none? matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } if matching_indexes.count > 1 raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}" elsif matching_indexes.none? raise ArgumentError, "No indexes found on #{table_name} with the options provided." else matching_indexes.first.name end end
def index_name_options(column_names)
def index_name_options(column_names) if expression_column_name?(column_names) column_names = column_names.scan(/\w+/).join("_") end { column: column_names } end
def indexes(table_name)
def indexes(table_name) raise NotImplementedError, "#indexes is not implemented" end
def insert_versions_sql(versions)
def insert_versions_sql(versions) sm_table = quote_table_name(schema_migration.table_name) if versions.is_a?(Array) sql = +"INSERT INTO #{sm_table} (version) VALUES\n" sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n") sql << ";" sql else "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});" end end
def internal_string_options_for_primary_key # :nodoc:
def internal_string_options_for_primary_key # :nodoc: { primary_key: true } end
def max_index_name_size
def max_index_name_size 62 end
def native_database_types
database types. See TableDefinition#column for details on the recognized
Returns a hash of mappings from the abstract data types to the native
def native_database_types {} end
def options_for_index_columns(options)
def options_for_index_columns(options) if options.is_a?(Hash) options.symbolize_keys else Hash.new { |hash, column| hash[column] = options } end end
def options_include_default?(options)
def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end
def primary_key(table_name)
def primary_key(table_name) pk = primary_keys(table_name) pk = pk.first unless pk.size > 1 pk end
def quoted_columns_for_index(column_names, options) # :nodoc:
def quoted_columns_for_index(column_names, options) # :nodoc: quoted_columns = column_names.each_with_object({}) do |name, result| result[name.to_sym] = quote_column_name(name).dup end add_options_for_index_columns(quoted_columns, **options).values.join(", ") end
def quoted_scope(name = nil, type: nil)
def quoted_scope(name = nil, type: nil) raise NotImplementedError end
def reference_name_for_table(table_name)
def reference_name_for_table(table_name) table_name.to_s.singularize end
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
to provide this in a migration's +change+ method so it can be reverted.
The +expression+ parameter will be ignored if present. It can be helpful
remove_check_constraint :products, name: "price_check", if_exists: true
use the +if_exists+ option.
To silently ignore a non-existent check constraint rather than raise an error,
remove_check_constraint :products, name: "price_check"
that does not exist will raise an error.
Removes the given check constraint from the table. Removing a check constraint
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options) return unless supports_check_constraints? return if if_exists && !check_constraint_exists?(table_name, **options) chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name at = create_alter_table(table_name) at.drop_check_constraint(chk_name_to_delete) execute schema_creation.accept(at) end
def remove_column(table_name, column_name, type = nil, **options)
if the column was already used.
column does not exist. This will silently ignore the migration rather than raising
If the options provided include an +if_exists+ key, it will be used to check if the
automatically removed or modified to remove this column from the index.
Depending on the database you're using, indexes using this column may be
In that case, +type+ and +options+ will be used by #add_column.
to provide these in a migration's +change+ method so it can be reverted.
The +type+ and +options+ parameters will be ignored if present. It can be helpful
remove_column(:suppliers, :qualification)
Removes the column from the table definition.
def remove_column(table_name, column_name, type = nil, **options) return if options[:if_exists] == true && !column_exists?(table_name, column_name) execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}" end
def remove_column_for_alter(table_name, column_name, type = nil, **options)
def remove_column_for_alter(table_name, column_name, type = nil, **options) "DROP COLUMN #{quote_column_name(column_name)}" end
def remove_columns(table_name, *column_names, type: nil, **options)
+type+ and other column options can be passed to make migration reversible.
remove_columns(:suppliers, :qualification, :experience)
Removes the given columns from the table definition.
def remove_columns(table_name, *column_names, type: nil, **options) if column_names.empty? raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") end remove_column_fragments = remove_columns_for_alter(table_name, *column_names, type: type, **options) execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_fragments.join(', ')}" end
def remove_columns_for_alter(table_name, *column_names, **options)
def remove_columns_for_alter(table_name, *column_names, **options) column_names.map { |column_name| remove_column_for_alter(table_name, column_name) } end
def remove_foreign_key(from_table, to_table = nil, **options)
[:to_table]
with an addition of
The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
remove_foreign_key :accounts, :branches, if_exists: true
don't exist.
Checks if the foreign key exists before trying to remove it. Will silently ignore indexes that
remove_foreign_key :accounts, name: :special_fk_name
Removes the foreign key named +special_fk_name+ on the +accounts+ table.
remove_foreign_key :accounts, to_table: :owners
Removes the foreign key on +accounts.owner_id+.
remove_foreign_key :accounts, column: :owner_id
Removes the foreign key on +accounts.owner_id+.
remove_foreign_key :accounts, :branches
Removes the foreign key on +accounts.branch_id+.
key so that the migration can be reverted properly.
It is recommended that you provide any options used when creating the foreign
will be used to re-add the foreign key in case of a migration rollback.
Removes the given foreign key from the table. Any option parameters provided
def remove_foreign_key(from_table, to_table = nil, **options) return unless use_foreign_keys? return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table) fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name at = create_alter_table from_table at.drop_foreign_key fk_name_to_delete execute schema_creation.accept(at) end
def remove_index(table_name, column_name = nil, **options)
Concurrently removing an index is not supported in a transaction.
Note: only supported by PostgreSQL.
remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
remove_index :accounts, if_exists: true
don't exist.
Checks if the index exists before trying to remove it. Will silently ignore indexes that
remove_index :accounts, :branch_id, name: :by_branch_party
Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
remove_index :accounts, name: :by_branch_party
Removes the index named +by_branch_party+ in the +accounts+ table.
remove_index :accounts, column: [:branch_id, :party_id]
Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
remove_index :accounts, column: :branch_id
Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
remove_index :accounts, :branch_id
Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
Removes the given index from the table.
def remove_index(table_name, column_name = nil, **options) return if options[:if_exists] && !index_exists?(table_name, column_name, **options) index_name = index_name_for_remove(table_name, column_name, options) execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" end
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
remove_reference(:products, :user, foreign_key: true)
====== Remove the reference with a foreign key
remove_reference(:products, :supplier, polymorphic: true)
====== Remove polymorphic reference
remove_reference(:products, :user, index: false)
====== Remove the reference
Removes the reference(s). Also removes a +type+ column if one exists.
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) conditional_options = options.slice(:if_exists, :if_not_exists) if foreign_key reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name if foreign_key.is_a?(Hash) foreign_key_options = foreign_key.merge(conditional_options) else foreign_key_options = { to_table: reference_name, **conditional_options } end foreign_key_options[:column] ||= "#{ref_name}_id" remove_foreign_key(table_name, **foreign_key_options) end remove_column(table_name, "#{ref_name}_id", **conditional_options) remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic end
def remove_timestamps(table_name, **options)
remove_timestamps(:suppliers)
Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
def remove_timestamps(table_name, **options) remove_columns table_name, :updated_at, :created_at end
def remove_timestamps_for_alter(table_name, **options)
def remove_timestamps_for_alter(table_name, **options) remove_columns_for_alter(table_name, :updated_at, :created_at) end
def rename_column(table_name, column_name, new_column_name)
rename_column(:suppliers, :description, :name)
Renames a column.
def rename_column(table_name, column_name, new_column_name) raise NotImplementedError, "rename_column is not implemented" end
def rename_column_indexes(table_name, column_name, new_column_name)
def rename_column_indexes(table_name, column_name, new_column_name) column_name, new_column_name = column_name.to_s, new_column_name.to_s indexes(table_name).each do |index| next unless index.columns.include?(new_column_name) old_columns = index.columns.dup old_columns[old_columns.index(new_column_name)] = column_name generated_index_name = index_name(table_name, column: old_columns) if generated_index_name == index.name rename_index table_name, generated_index_name, index_name(table_name, column: index.columns) end end end
def rename_column_sql(table_name, column_name, new_column_name)
def rename_column_sql(table_name, column_name, new_column_name) "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" end
def rename_index(table_name, old_name, new_name)
rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
Renames an index.
def rename_index(table_name, old_name, new_name) old_name = old_name.to_s new_name = new_name.to_s validate_index_length!(table_name, new_name) # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) remove_index(table_name, name: old_name) end
def rename_table(table_name, new_name, **)
rename_table('octopuses', 'octopi')
Renames a table.
def rename_table(table_name, new_name, **) raise NotImplementedError, "rename_table is not implemented" end
def rename_table_indexes(table_name, new_name)
def rename_table_indexes(table_name, new_name) indexes(new_name).each do |index| generated_index_name = index_name(table_name, column: index.columns) if generated_index_name == index.name rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) end end end
def schema_creation # :nodoc:
object and return DDL.
Returns an instance of SchemaCreation, which can be used to visit a schema definition
def schema_creation # :nodoc: SchemaCreation.new(self) end
def strip_table_name_prefix_and_suffix(table_name)
def strip_table_name_prefix_and_suffix(table_name) prefix = Base.table_name_prefix suffix = Base.table_name_suffix table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s end
def table_alias_for(table_name)
def table_alias_for(table_name) table_name[0...table_alias_length].tr(".", "_") end
def table_comment(table_name)
def table_comment(table_name) nil end
def table_exists?(table_name)
table_exists?(:developers)
Checks to see if the table +table_name+ exists on the database.
def table_exists?(table_name) query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present? rescue NotImplementedError tables.include?(table_name.to_s) end
def table_options(table_name)
def table_options(table_name) nil end
def tables
def tables query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA") end
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc: type = type.to_sym if type if native = native_database_types[type] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale scale ||= native[:scale] if precision ||= native[:precision] if scale column_type_sql << "(#{precision},#{scale})" else column_type_sql << "(#{precision})" end elsif scale raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" end elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision] if (0..6) === precision column_type_sql << "(#{precision})" else raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6" end elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) column_type_sql << "(#{limit})" end column_type_sql else type.to_s end end
def update_table_definition(table_name, base) # :nodoc:
def update_table_definition(table_name, base) # :nodoc: Table.new(table_name, base) end
def use_foreign_keys?
def use_foreign_keys? supports_foreign_keys? && foreign_keys_enabled? end
def valid_column_definition_options # :nodoc:
def valid_column_definition_options # :nodoc: ColumnDefinition::OPTION_NAMES end
def valid_primary_key_options # :nodoc:
def valid_primary_key_options # :nodoc: [:limit, :default, :precision] end
def valid_table_definition_options # :nodoc:
def valid_table_definition_options # :nodoc: [:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation] end
def validate_change_column_null_argument!(value)
def validate_change_column_null_argument!(value) unless value == true || value == false raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}" end end
def validate_create_table_options!(options)
def validate_create_table_options!(options) unless options[:_skip_validate_options] options .except(:_uses_legacy_table_name, :_skip_validate_options) .assert_valid_keys(valid_table_definition_options, valid_primary_key_options) end end
def validate_index_length!(table_name, new_name, internal = false)
def validate_index_length!(table_name, new_name, internal = false) if new_name.length > index_name_length raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" end end
def validate_table_length!(table_name)
def validate_table_length!(table_name) if table_name.length > table_name_length raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters" end end
def view_exists?(view_name)
view_exists?(:ebooks)
Checks to see if the view +view_name+ exists on the database.
def view_exists?(view_name) query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present? rescue NotImplementedError views.include?(view_name.to_s) end
def views
def views query_values(data_source_sql(type: "VIEW"), "SCHEMA") end