rails migration unique true but allow null values - ruby-on-rails

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

Related

Rails Active Storage not working every time. Some time it works, sometime it doesn't

I am using Active Storage to store images for match score. My class looks like this
class TournamentMatch < ApplicationRecords
has_many_attached :score_cards
end
My Score card controller looks like this
class Public::ScoreCardsController < Public::BaseController
def new
session[:tournament_match_id] = params[:tournament_match_id]
end
def create
tournament_match_id = session[:tournament_match_id]
tournament_id = session[:tournament_id]
session[:tournament_match_id] = nil
session[:tournament_id] = nil
tournament_match = TournamentMatch.find_by(id: tournament_match_id)
if tournament_match.score_cards.attach(params['score_cards'])
flash[:notice] = 'Score card uploaded'
else
flash[:error] = 'Score card upload failed'
end
redirect_to public_results_path(tournament_id: "#{tournament_id}/", anchor: 'results')
end
def view
#tournament_match = TournamentMatch.find_by(id: params[:tournament_match_id])
render layout: "application"
end
end
So in my create action,I am finding TournamentMatch by id and then attaching score card to it. I am using same image file each time for uploading. But sometimes image is getting attached and sometime it doesn't. tournament_match.score_cards.attach(params['score_cards'] always returns true in controller. But if i find the same tournament_match and run tournament_match.score_cards.attach(params['score_cards'] in console then it's returning false. I have also noticed that record_id for active_storage_attachments is set to 0 when ever image is not saved. But if image is saved then record_id have value greater than 0.
I have run out of ideas for debugging. Please suggest me how to debug this problem.
Update: below query is from log file, it shows record_id is nil
ActiveStorage::Attachment Exists? (0.6ms) SELECT 1 AS one FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", nil], ["record_type", "TournamentMatch"], ["name", "score_cards"], ["LIMIT", 1]]
My main problem was that I am using :uuid for primary key for all my tables so I has to edit ActiveStorage migration to use :uuid. I had changed mainly these 2 lines t.references :record, null: false, polymorphic: true, index: false, type: :uuid and t.references :blob, null: false, type: :uuid. My final migration looks like this. And after this ActiveStorage is now woking fine.
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change
create_table :active_storage_blobs, id: :uuid do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: :uuid do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
t.references :blob, null: false, type: :uuid
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
For new rails 7 apps, inside the ActiveStorage migration file, change this
primary_key_type, foreign_key_type = primary_and_foreign_key_types
to this
primary_key_type, foreign_key_type = [:uuid, :uuid]
It will work now.
Notes
I think this means if you want attachments on any model that has uuid as its primary key, then you'll need to make sure all models which you intend to have attachments also use uuid i.e. you can't mix and match (at least, I don't know how).
See here for how to set uuid as the default primary key for all generators across the application.
References
The ActiveStorage docs says:
If you are using UUIDs instead of integers as the primary key on your models you will need to change the column type of active_storage_attachments.record_id and active_storage_variant_records.id in the generated migration accordingly.
More reading here for older rails versions.

Rails 5: custom slugs for rails urls error

I was following the tutorial on hackernoon, to generate Obfuscated URLs.
The first step is to add a slug column to the database, but I got an error.
AddSlugToReservations
class AddSlugToReservation < ActiveRecord::Migration[5.2]
def change
add_column :reservations, :slug, :string, null: false
add_index :reservations, :slug, unique: true
end
end
I get the following error when I try rails db:migrate
SQLite3::SQLException: Cannot add a NOT NULL column with default value NULL: ALTER TABLE "reservations" ADD "slug" varchar NOT NULL
So I change the migration file to:
class AddSlugToReservation < ActiveRecord::Migration[5.2]
def change
add_column :reservations, :slug, :string, null: false, default: 0
change_column :reservations, :slug, :string, default: nil
add_index :reservations, :slug, unique: true
end
end
But then I encountered the following error:
SQLite3::ConstraintException: UNIQUE constraint failed: reservations.slug: CREATE UNIQUE INDEX "index_reservations_on_slug" ON "reservations" ("slug")
What should I do? I couldn't find any solution to this...
You can do this with below code
class AddSlugToReservation < ActiveRecord::Migration[5.0]
def change
add_column :reservations, :slug, :string, unique: true, default: 0, null: false
end
end
try this
class AddSlugToReservation < ActiveRecord::Migration[5.2]
def change
add_column :reservations, :slug, :string, unique: true
change_column_null :reservations, :slug, false
end
end
Hope it helps.

can anyone explain me this error?

I am getting this while i use Postgres.
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::UndefinedTable: ERROR: relation "retailers" does not exist
: ALTER TABLE "stations" ADD CONSTRAINT "fk_rails_57ee36b830"
FOREIGN KEY ("retailer_id")
REFERENCES "retailers" ("id")
/home/suyesh/Desktop/Petrohub_main/db/migrate/20160104152245_create_stations.rb:3:in `change'
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: relation "retailers" does not exist
: ALTER TABLE "stations" ADD CONSTRAINT "fk_rails_57ee36b830"
FOREIGN KEY ("retailer_id")
REFERENCES "retailers" ("id")
/home/suyesh/Desktop/Petrohub_main/db/migrate/20160104152245_create_stations.rb:3:in `change'
PG::UndefinedTable: ERROR: relation "retailers" does not exist
/home/suyesh/Desktop/Petrohub_main/db/migrate/20160104152245_create_stations.rb:3:in `change'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Here are my stations and retailers model.
Retailer
class Retailer < User
has_many :stations
has_many :retailer_suppliers
has_many :suppliers , through: :retailer_suppliers, as: :connections
end
Stations
class Station < ActiveRecord::Base
belongs_to :retailer
has_many :tanks
end
Here is my Station Migration
class CreateStations < ActiveRecord::Migration
def change
create_table :stations do |t|
t.string :brand
t.string :business_name
t.string :tax_id
t.string :phone_number
t.string :contact_person
t.string :cell_number
t.string :address1
t.string :address2
t.string :city
t.string :state
t.string :zip
t.string :station_reg_number
t.references :retailer, index: true, foreign_key: true
t.timestamps null: false
end
end
end
I dont have retailer migration because its inheriting from the User. Here is my User migration
User
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
Add Type to User
class AddTypeToUsers < ActiveRecord::Migration
def change
add_column :users, :type, :string
end
end
Add extra attributes to user
class AddExtraToUsers < ActiveRecord::Migration
def change
add_column :users, :first_name, :string
add_column :users, :last_name, :string
add_column :users, :phone_number, :string
add_column :users, :cell_number, :string
add_column :users, :tax_id, :string
add_column :users, :business_name, :string
add_column :users, :address1, :string
add_column :users, :address2, :string
add_column :users, :city, :string
add_column :users, :state, :string
add_column :users, :zip_code, :string
add_column :users, :years_in_business, :string
end
end
And add account number to user
class AddAccountNumberToUsers < ActiveRecord::Migration
def change
add_column :users, :account_number, :string
add_index :users, :account_number
end
end
It works with no errors when i use Sqlite3 but i get errors in Production heroku using postgres. so i decided to use postgres in developemnt and i saw the above errors which i cannot understand. Thank you in advance
PG::UndefinedTable: ERROR: relation "retailers" does not exist
This error simply means that the retailers table is not present in your database when you try to reference this in another place. All you need is to make sure you create retailers table before you try to use/reference it in some some migration.
The error is coming from this line in your migration:
t.references :retailer, index: true, foreign_key: true
The foreign key option means that rails is trying to create a foreign key between retailer_id on the stations table and id on the non existant retailers table.
Although migrations are frequently created at the same time as a model, they're not really connected - the migration doesn't know that retailer is an STI model.
As far as I can tell you will need to remove the foreign key option from the call to references and add the foreign separately with add_foreign_key:
add_foreign_key :stations, :users, column: "retailer_id"
You probably didn't encounter this in development because earlier versions of sqlite3 didn't support foreign keys and current versions require that it be activated (see docs): unless activated it just ignores foreign key constraints (as long as they are syntactically correct)

Changing fields to NOT NULL with NULL fields using migrations

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

Rails Migration with Change_column says successful, but silently fails

My schema:
create_table "location_hours", force: true do |t|
t.datetime "start_at"
t.datetime "end_at"
t.integer "location_id"
t.datetime "created_at"
t.datetime "updated_at"
end
My migration:
class ChangeLocationHourNulls < ActiveRecord::Migration
def change
change_column :location_hours, :start_at, :datetime, :null => :false
change_column :location_hours, :end_at, :datetime, :null => :false
change_column :location_hours, :location_id, :integer, :null => :false
end
end
Rake Output:
$ bundle exec rake db:migrate
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
== ChangeLocationHourNulls: migrating =======================================
-- change_column(:location_hours, :start_at, :datetime, {:null=>:false})
-> 0.0008s
-- change_column(:location_hours, :end_at, :datetime, {:null=>:false})
-> 0.0006s
-- change_column(:location_hours, :location_id, :integer, {:null=>:false})
-> 0.0032s
== ChangeLocationHourNulls: migrated (0.0067s) ==============================
-> 0.0032s
== ChangeLocationHourNulls: migrated (0.0067s) ==============================
When I check my schema file, it hasn't changed, and the database hasn't changed. Any ideas on what could cause this?
Rollback the migration ChangeLocationHourNulls.
Then change your migration as below:
class ChangeLocationHourNulls < ActiveRecord::Migration
def change
change_column :location_hours, :start_at, :datetime, :null => false
change_column :location_hours, :end_at, :datetime, :null => false
change_column :location_hours, :location_id, :integer, :null => false
end
end
Use false and not :false.
Run rake db:migrate
I believe #Kirti advise should solve the issue but i just realized there is a better option in your case, since you just want to change Nullable option:
You could use change_column_null which works like this:
class ChangeLocationHourNulls < ActiveRecord::Migration
def change
change_column_null :location_hours, :start_at, false
change_column_null :location_hours, :end_at, false
change_column_null :location_hours, :location_id, false
end
end

Resources