Referencing to Rails 4.2 add_foreign_key support:
# add a foreign key to `articles.author_id` referencing `authors.id`
add_foreign_key :articles, :authors
How to create a nullable foreign key constraint, to allow the situation, where articles.author_id can be sometimes null?
Note that in Rails 5 and in Rails 6 you may need to mark the corresponding association as optional if it's 1:n (belongs_to), as the default was changed:
belongs_to :author, optional: true
This is the corresponding Changeset.
To use the old behavior across your application, you can also set:
Rails.application.config.active_record.belongs_to_required_by_default = false
in config/initializers/new_framework_defaults.rb
The error you will usually see is:
ActiveRecord::RecordInvalid: Validation failed: Class must exist
from /usr/local/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0.1/lib/active_record/validations.rb:78:in `raise_validation_error'
You may also need to update any migration: change null: false to true and run rake db:redo if it had already run.
Adding optional: true along with belongs_to :author in article model will do the job.
There is nothing in the guide that implies add_foreign_key would make the corresponding foreign field "NOT NULL" or required. add_foreign_key simply adds a foreign key constraint whether the field is required or not (in your case author_id in articles).
Did you get an error when you tried this in your migration?
Here's the SQL that it would generate:
ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
SO, if in your original migration of articles, author_id is null, then you can have foreign key that's nullable.
Related
What is the correct way to add reference column migration in Rails 6 without getting SQLite3::SQLException: Cannot add a NOT NULL column with default value NULL?
I can hack it to get it working; but, I am preparing a tutorial for a grad class, so I want to make sure I'm doing it "by the book".
The starting point is a Post class (think "blog post"). I want to add an Author class and set up an 1-to-many relationship between authors and posts. After adding the author class and running the corresponding migration, I then create a migration to add an Author reference to Post:
rails g migration AddAuthorToPost author:references
This command generates:
class AddAuthorToPost < ActiveRecord::Migration[6.0]
def change
add_reference :posts, :author, null: false, foreign_key: true
end
end
The problem is, of course, that SQLite complains because it won't tolerate the potential for a null foreign key --- even if the Post table is empty: (How to solve "Cannot add a NOT NULL column with default value NULL" in SQLite3?)
I looked back at the previous year's tutorial (prepared by a different instructor) and the generator did not add null: false to the migration. (See also Add a reference column migration in Rails 5)
Removing null: false from the migration allows the migration to run; but, "disabling safety features" doesn't seem appropriate in a classroom setting :)
Is there a better way to do this?
By default, a foreign_key is required (on app level, not on database).
To disable, in config/application.rb add
config.active_record.belongs_to_required_by_default = false
Or
class YourModel < ApplicationRecord
belongs_to :another_model, optional: true
end
Can add_foreign_key add a 'NOT VALID' parameter to the ALTER TABLE command? (Postgres, if it mattters)
I have a foreign key between two very large tables. I need to add CASCADE DELETE to the key. It seems Postgres does not support adding the cascade to an existing foreign key. The solution is to drop the key, and add a new one with the cascade.
Easy enough...except that checking the constraint takes a LONG time. I'm not sure about any locking going on during that check, but really, I just want to skip it. The data is valid before I drop the constraint...it will be valid a moment later.
Postgres supports this with ALTER TABLE ADD CONSTRAINT ... NOT VALID. (Which means "skip validation", even though it sounds like you're saying that it is not valid. :shrug: )
So, can I get add_foreign_key to not validate?
From the manual: ActiveRecord::ConnectionAdapters::SchemaStatements#add_foreign_key
:validate
(Postgres only) Specify whether or not the constraint should be validated. Defaults to true.
So, to add a cascade delete to an existing foreign key constraint with no downtime:
remove_foreign_key :address, column: :user_id
add_foreign_key :address, :user_id, :users, on_delete: :cascade, validate: false
I initially searched for this answer with add_foreign_key "NOT VALID", for which I got no useful hits in the docs, StackOverflow, etc. Hopefully this question and answer will help me find this answer again next time I need it.
I have a model called WalksSection and another called WalkSectionButton. I have created the WalksSection model (and done migration).
Now I want to create WalkSectionButton but want to create two references between the two tables:
walk_sections.id = walk_section_buttons.section_button_id
walk_sections.id = walk_section_buttons.next_section_button_id
The first is the standard one but the other (I think) I need to tell the migration and the model the column name.
So the standard one in the model is
belongs_to :WalkSection
and in the migration is
t.references :WalkSection, foreign_key: true
So I guess I need to have a second line in model and migration which specifies the foreign (and primary) key name but not sure of the syntax.
The other way to do this maybe is to run the migration to create the first reference then do another migration that adds the column and key for the second foregn key.
add_column :walk_section_buttons, :next_section_button_id, :integer
add_foreign_key :walk_section_buttons, :walk_sections, column: :next_section_button_id, primary_key: :id
But not sure what to do to the model. Maybe I don't need to but in the WalksSections model I think I should add a has_many, but again would need to specify a column name for the second one.
I'm pushing a working app (developed it locally) to a Heroku environment. When trying to run db:migrate on the production environment, the migrations fail because the postgreSQL seems to be doing some sanity checks that my sqlite on localhost wasn't doing, for instance:
I created a migration for a post model and already defined a relationship to a (at that point) non existing comment model. Postgre is complaining that the table comment does not exist. (solved this by rearranging the migrations, bad I know)
I use a sort of STI trick in my model in that the models Exercise, Video & Lesson all store their data in the same table (steps). Exercise has many Questions but when trying to create the table, Postgre is complaining about a missing table exercises.
Is there a way to fix the second error or is there a general way to prevent rails from the sanity checks (does the related table exist)?
EDIT:
Ok, so the relevant migration is this one:
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.string :title
t.references :exercise, index: true, foreign_key: true
t.text :content
t.timestamps null: false
end
end
end
The problem with this is that there's no table exercises, because that one is named steps. So the referential integrity is breached for PostgreSQL resulting in the following error:
== 20151208132820 CreateQuestions: migrating ==================================
-- create_table(:questions)
(14.1ms) CREATE TABLE "questions" ("id" serial primary key, "title" character varying, "exercise_id" integer, "content" text, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
(5.2ms) CREATE INDEX "index_questions_on_exercise_id" ON "questions" ("exercise_id")
(9.3ms) ALTER TABLE "questions" ADD CONSTRAINT "fk_rails_5ba13b3a6e"
FOREIGN KEY ("exercise_id")
REFERENCES "exercises" ("id")
PG::UndefinedTable: ERROR: relation "exercises" does not exist
: ALTER TABLE "questions" ADD CONSTRAINT "fk_rails_5ba13b3a6e"
FOREIGN KEY ("exercise_id")
REFERENCES "exercises" ("id")
The foreign key constraint isn't needed. I would always recommend using the same database on your localhost as you will on your production server.
Remove the foreign key, deploy and the migration will work :)
I do think that the problem is coming from the foreign_key constraint, you can just remove it and check again.
A better solution is to mimic the production environment locally and make it work before pushing up.
I am trying to use mailboxer in my rails 4 app. A problem is arising when i try to deploy the db. The error occurs in creating the mailboxer conversations table, which has dependencies in notifications table.
I am trying to remove the foreign key for notifications conversations.
I created a migration which says:
change_table :notifications do |t|
t.remove_foreign_key :conversations
However, the rake aborts and says a foreign key does not exist.
rake aborted!
An error has occurred, this and all later migrations canceled:
PG::UndefinedObject: ERROR: constraint "notifications_conversation_id_fk" of relation "notifications" does not exist
My schema includes:
add_foreign_key "notifications", "conversations", name: "notifications_on_conversation_id"
I tried to rake db:migrate:down the original migration that created mailboxer, but also got an error saying 'command not found'.
Can anyone help? Thank you.
# Removes the given foreign key from the table.
# Removes the foreign key on +accounts.branch_id+.
remove_foreign_key :accounts, :branches
# Removes the foreign key on +accounts.owner_id+.
remove_foreign_key :accounts, column: :owner_id
# Removes the foreign key named +special_fk_name+ on the +accounts+ table.
remove_foreign_key :accounts, name: :special_fk_name
Offical doc: http://api.rubyonrails.org/v4.2/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_foreign_key
The add_foreign_key command in your schema gave your foreign key the name notifications_on_conversation_id. This name is different than the default name that foreigner would normally assign based on the column name, which is notifications_conversation_id_fk. So your remove_foreign_key command must specify the existing foreign key name instead of the column name. Try:
remove_foreign_key :notifications, name: "notifications_on_conversation_id"
When I ran that I got:
NoMethodError: undefined method `remove_foreign_key' for #<ActiveRecord::ConnectionAdapters::Table:0x00007faa35e94aa8>
Did you mean? remove_index
Words of wisdom- never use anything except an id integer for a foreign key. I used a title param while practicing on a fake app and it causes:
ActiveRecord::AssociationTypeMismatch (Company(#70210936585940) expected, got "Company4" which is an instance of String(#70210933923380))