Rails 5.2 Error Changing or Removing Table Column (SQLite3::ConstraintException: FOREIGN KEY constraint failed: DROP TABLE)
UPDATE:
A new column default can be added via Rails without having to use the database. In the Blog
model, we can use ActiveRecord::Attributes::ClassMethods::attribute to redefine the default value for freebie_type
:
attribute :freebie_type, :string, default: 'None'
This will change the default at the business logic level. Therefore, it is dependent on using ActiveRecord to be recognized. Manipulation of the database via SQL will still use the old default. To update the default in all cases see the original answer below.
ORIGINAL ANSWER:
Unfortunately, ALTER COLUMN
is only minimally supported by SQLite. The work around it to create a new table, copy the information to it, drop the old table, and finally rename the new table. This is what Rails is attempting to do, but without first disabling the foreign key constraints. The foreign key relations to user_id
, image_id
, and pdf_id
are preventing the table deletion.
You will need to do the update manually, either with SQL (preferred) or ActiveRecord::Base.connection
. You can see the process here under 'Modify column in table'. You can find all the options available for columns in the SQLite Create Table Documentation.
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE table1 RENAME TO _table1_old;
CREATE TABLE table1 (
( column1 datatype [ NULL | NOT NULL ] DEFAULT (<MY_VALUE>),
column2 datatype [ NULL | NOT NULL ] DEFAULT (<MY_VALUE>),
...
);
INSERT INTO table1 (column1, column2, ... column_n)
SELECT column1, column2, ... column_n
FROM _table1_old;
COMMIT;
PRAGMA foreign_keys=on;
Be certain that you have all the columns set up the way you want as you will not be able to fix it after the table is created! Going forward I would highly recommend setting up either a PostgreSQL or MySQL2 database. They are much more powerful and will be much easier to modify and maintain.
You can add an initializer to monkey patch the sqlite adapter to make it work with rails 5, just make sure you have sqlite >= 3.8, with this code:
blog/config/initializers/sqlite3_disable_referential_to_rails_5.rb
Content:
require 'active_record/connection_adapters/sqlite3_adapter'
module ActiveRecord
module ConnectionAdapters
class SQLite3Adapter < AbstractAdapter
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
old_foreign_keys = query_value("PRAGMA foreign_keys")
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
begin
execute("PRAGMA defer_foreign_keys = ON")
execute("PRAGMA foreign_keys = OFF")
yield
ensure
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
end
end
def insert_fixtures_set(fixture_set, tables_to_delete = [])
disable_referential_integrity do
transaction(requires_new: true) do
tables_to_delete.each {|table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete"}
fixture_set.each do |table_name, rows|
rows.each {|row| insert_fixture(row, table_name)}
end
end
end
end
private
def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
caller = lambda {|definition| yield definition if block_given?}
transaction do
disable_referential_integrity do
move_table(table_name, altered_table_name,
options.merge(temporary: true))
move_table(altered_table_name, table_name, &caller)
end
end
end
end
end
end
Here is the gist: https://gist.github.com/dante087/3cfa71452229f8125865a3247fa03d51