Changing fields to NOT NULL with NULL fields using migrations - ruby-on-rails

So I mistakenly created a table with fields which should have been NOT NULL.
I need to create a migration to change the fields from NULLABLE to NOT NULL, but some rows exist which are already NULL.
Hoe can I update these rows and change the fields? I tried this:
def change
change_column :countries, :effective_date, :date, :null => false, :default => Time.now
change_column :countries, :expiry_date, :date, :null => false, :default => Time.new(9999,12,31)
end
But this failed with an error:
Mysql2::Error: Invalid use of NULL value: ALTER TABLE
Any ideas? Needs to work on both mysql and sql server..

First ensure there are no NULLs and then change the constraint.
Option 1:
Country.where(effective_date: nil).update_all(effective_date: Time.now)
Country.where(expiry_date: nil).update_all(expiry_date: Time.new(9999,12,31))
change_column :countries, :effective_date, :date, :null => false
change_column :countries, :expiry_date, :date, :null => false
Option 2:
change_column_null :countries, :effective_date, false, Time.now
change_column_null :countries, :expiry_date, false, Time.new(9999,12,31)

You can set the default value for all NULL columns before adding the constraint, for example:
execute "UPDATE study_sites SET patients_recruited = 0 WHERE patients_recruited IS NULL"
change_column :study_sites, :patients_recruited, :integer, default: 0, null: false

Related

Rails Change Column to Accept Null and Change Column Type

Is there a way I can perform both of these in 1 migration? I'm using Rails 5 and want to change it from a string to text but also allow nulls. It seems like the following below only allows the null but did not change the column type
def up
change_column_null :animals, :title, :text, :default => nil
change_column_null :animals, :description, :text, :default => nil
end
def down
change_column :animals, :title, :string
change_column :animals, :description, :string
end
By default, string and text data types allows null values, also it's their default value, unless you have previously defined some constraint
Anyway, being explicit, this should work:
def up
change_column :animals, :title, :text, null: true, default: nil
change_column :animals, :description, :text, null: true, default: nil
end
def down
change_column :animals, :title, :string
change_column :animals, :description, :string
end

rails migration unique true but allow null values

I originally had this migration:
def change
add_column :users, :provider, :string, null: false, default: "email"
add_column :users, :uid, :string, null: false, default: ""
add_index :users, [:uid, :provider], unique: true
end
But in my application, users can sign in with both omniauth and username and password without oauth authentication. As a result, in many situations, uid and provider will be null. So I created the following migration:
def change
change_column_default :users, :provider, nil
change_column_default :users, :uid, nil
end
But when I try to set the provider or uid to nil in Rails, I get a PG::NotNullViolation: ERROR:
u = User.first
u.provider = nil
u.save!
(0.4ms) BEGIN
SQL (1.5ms) UPDATE "users" SET "updated_at" = $1, "provider" = $2 WHERE "users"."id" = $3 [["updated_at", 2017-08-16 00:01:34 UTC], ["provider", nil], ["id", 1]]
(0.5ms) ROLLBACK
ActiveRecord::StatementInvalid: PG::NotNullViolation: ERROR: null value in column "provider" violates not-null constraint
DETAIL: Failing row contains
It appears unique: true in the migration prevents setting null values. How can I get around this?
You have set the columns to null: false in your first migration which is causing the PG::NotNullViolation exception. That needs to be set to true to allow null values in both the columns. Writing a new migration to set null: true should resolve the issue as follows.
def change
change_column_null :users, :provider, true
change_column_null :users, :uid, true
end
Also below index may not work(RecordNotUnique exception raises as it is set unique: true) as you will have multiple rows having both uid and provider with null values. So this index need to be dropped.
add_index :users, [:uid, :provider], unique: true
In addition to this:
def change
change_column :users, :provider, :string, null: true
change_column :users, :uid, :string, null: true
remove_index :users, [:uid, :provider]
end
which would subsequently allow null values but eliminate the two field constraint at the database-level, I did this at the model-level:
validates :provider, uniqueness: { scope: :uid }, allow_nil: true

Change multiple columns with one single migration change

I have the following migration. Is there a way to run these changes in one change than 3?
def change
change_column :comments, :attr_1, :string, null: true
change_column :comments, :attr_2, :string, null: true
change_column :comments, :attr_3, :string, null: true
end
The short answer is no. The change_column method is configured to take arguments for table name, column name, and an options hash. The source code for change_column can be found here: https://github.com/rails/rails/blob/0fe76197d2622674e1796a9a000995a7a1f6622b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
The only line in the change_column method is:
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
which executes an ALTER script on the table name passed as the first argument. You can't pass an array of tables/columns as arguments, so you have to do them one at a time. This paradigm is fairly typical in Rails migrations.
You can use the change_table function with bulk set to true which will run the alter as a single MySQL query and be much faster than running individually. Example is below.
def change
change_table(:comments, bulk: true) do |t|
t.change :attr_1, :string, null: true
t.change :attr_2, :string, null: true
t.change :attr_3, :string, null: true
end
end

How to update reference column in Ruby

I have a migration that I want to make with a reference in my table. I create the reference using this:
create_table :user_events do |t|
t.references :user, :null => false
end
And in my migration, I want to be able to allow the reference to be NULL.
def self.up
change_column :user_events, :user, :integer, :null => true
end
However I keep getting PGError: ERROR: column "user" of relation "user_events" does not exist. Am I migrating wrong?
This should work:
def self.up
change_column :user_events, :user_id, :integer, :null => true
end
Note that the column you're trying to change is called user_id, not user
It's because your migration creates a column named user_id, referencing the User model.
try
def self.up
change_column :user_events do |c|
c.references :user, :integer, :null => true
end
end

Problem with composite_primary_key Gem in Rails

I'm using the composite primary keys gem from drnic and I've got a problem with it:
If I want to create a CourseOrder (with has a cpk) with the following command in my tests: course_order = CourseOrder.new(:daily_order => daily_orders(:daily_order_one_of_today_for_hans),:course => courses(:non_veg_2), :finished => false, :ordered_at => Time.now) I'll get the following error:
ActiveRecord::StatementInvalid: PGError: ERROR: null value in column "course_iddaily_order_id" violates not-null constraint
: INSERT INTO "course_orders" ("course_iddaily_order_id", "course_id", "ordered_at", "finished", "daily_order_id") VALUES (NULL, 489519433, '2011-03-01 10:19:27.169397', 'f', 594369222) RETURNING "course_iddaily_order_id"
I think that there is a course_iddaily_order_id is wrong, isn't it?
My Migration file looks like this:
class AddCourseOrder < ActiveRecord::Migration
def self.up
create_table :course_orders, :primary_key => [:course_id, :daily_order_id] do |table|
table.integer :course_id
table.integer :daily_order_id
table.datetime :ordered_at, :null => false
table.boolean :finished, :default => false, :null => false
end
end
def self.down
drop_table :course_orders
end
end
The resulting schema part looks like this:
create_table "course_orders", :primary_key => "course_iddaily_order_id", :force => true do |t|
t.integer "course_id", :null => false
t.integer "daily_order_id", :null => false
t.datetime "ordered_at", :null => false
t.boolean "finished", :default => false, :null => false
end
My Model looks like this:
class CourseOrder < ActiveRecord::Base
set_primary_keys :course_id, :daily_order_id
belongs_to :course
belongs_to :daily_order
end
I don't know whats wrong here, can you help?
I guess the create_table method doesn't takes an array for :primary_key option. I guess you can omit setting it.

Resources