foreigner - remove foreign key - ruby-on-rails

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))

Related

How do I drop a foreign key if it exists in Ruby on Rails?

There's a function called index_exists? in ActionRecord, but no foreign_key_exists? on Rails 4.2.7.
So when I call remove_foreign_key :parties, :franchise_groups on some databases it breaks.
What should I use?
Update
My code
class RemoveForeignKey < ActiveRecord::Migration
def up
if foreign_key_exists?(:parties, :franchise_groups)
remove_foreign_key :parties, :franchise_groups
end
end
end
gives the error
== 20161107163800 RemoveForeignKey: migrating =================================
-- foreign_key_exists?(:parties, :franchise_groups)
rake aborted!
An error has occurred, all later migrations canceled:
undefined method `foreign_key_exists?' for #<RemoveForeignKey:0x00000007ea0b58>
/home/rje/.rvm/gems/ruby-2.3.0/gems/activerecord-4.2.7/lib/active_record/migration.rb:664:in `block in method_missing'
but no foreign_key_exists?
There is foreign_key_exists? :)
Checks to see if a foreign key exists on a table for a given foreign
key definition.
# Checks to see if a foreign key exists.
foreign_key_exists?(:accounts, :branches)
# Checks to see if a foreign key on a specified column exists.
foreign_key_exists?(:accounts, column: :owner_id)
# Checks to see if a foreign key with a custom name exists.
foreign_key_exists?(:accounts, name: "special_fk_name")
Alternatively, you can use foreign_keys:
if foreign_keys(:table_name).include?(foreign_key_name)
# do stuff
end
On Rails 4 there is no foreign_key_exists so I came up with the following solution:
remove_foreign_key :events, column: :subscribers_selector_id if foreign_keys(:events).map(&:column).include?("subscribers_selector_id")
I think you can use something like this
def up
remove_foreign_key :parties, column: :franchise_groups
end
def down
add_foreign_key :parties, :franchise_groups
end
It works on connection:
ActiveRecord::Base.connection.foreign_key_exists?(:parties, :franchise_groups)
My Rails version doesn't seem to have "foreign_key_exists?" (Rails 4.2.6), so I'm using Array#any? to search through the results from "foreign_keys" and determine whether a given foreign key exists:
foreign_keys("parties").any?{|k| k[:to_table] == "franchise_groups"}
You can use it as such:
if foreign_keys("parties").any?{|k| k[:to_table] == "franchise_groups"}
remove_foreign_key :parties, column: :franchise_group_id
end
Rails 7+ if_exists / if_not_exists options
Rails 7 adds if_exists option to remove_foreign_key in order to not raise an error when the foreign key is already removed.
Rails 7 adds if_not_exists option to add_foreign_key in order to not raise an error when the foreign key is already added.
As a result, a migration can be written in the following way:
class RemoveFranchiseGroupForeignKeysFromParties < ActiveRecord::Migration
def up
remove_foreign_key :parties, :franchise_groups, if_exists: true
end
def down
add_foreign_key :parties, :franchise_groups, if_not_exists: true
end
end
Sources:
Add support for if_exists/if_not_exists on remove_foreign_key/add_foreign_key.
remove_foreign_key docs.
add_foreign_key docs.

Rails 4 - Why do index records remain after destroy_all?

I have a user model in my rails 4 app.
I have gone into my console and done:
User.destroy_all
User.count verifies to me that there are no users. I then try to sign up again, which I am allowed to do, but once I confirm my email and try to sign in, I get an error that says:
ERROR: duplicate key value violates unique constraint "index_users_on_email"
DETAIL: Key (email)=(angus#gmail.com) already exists.
How can I fix this index records so that those are also deleted when the User.destroy_all method is run?
To ensure you don't have this problem, make sure you delete all the relevant data when destroying a User.
class UpdateUserForeignKey < ActiveRecord::Migration
def change
remove_foreign_key :users, :email
add_foreign_key :users, :email, on_delete: :cascade
end
end
More about Rails Foreign Keys
http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_foreign_key

Running db:migrate causes some 'validations' on production, can that be turned off?

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.

Add nullable foreign key in Rails

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.

Shoulda tests failing on model with no id

I created a new model in my rails app. Since it's a one-to-one relation with another table, there's no need for the new model to have an id column. Everything is working fine, but for some reason, all of my Shoulda tests on this model are failing. For example:
should_validate_presence_of :first_name
is throwing this error:
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'my_new_table.id'
in 'field list': SELECT `my_new_table`.id FROM `my_new_table` WHERE
(`my_new_table`.`some_other_column` IS NULL) LIMIT 1
Does Shoulda require an ID column? If so, is there a way around this?
Did you alias the primary key for Rails purposes?
set_primary_key :my_fk_id
Rails expects your models to have an integer id column as a primary key. You can set it to the foreign key like #MattMcKnight suggests but I'd recommend you create the id column even if you don't strictly need it. In my experience it will save you tons of headaches like this, with basically no downside.
Did you run rake db:migrate and rake db:test:prepare after you created the new model?
All tables require an id column that's just the way that ActiveRecord works. As MattMcKnight points out, you can designate another column as the primary key. You can also specify that a table have no id column is creatied by providing :id => false as an option to create_table.
Matt's answer covers renaming the primary key in the model definition. Here's how to do it in a migration.
Note, providing the primary key option without giving the id option as false means that you do not have to add to column's definition to the block. Rails will create it for you. And automatically use it in any join tables.
Example:
class CreateTableWithOUtID < ActiveRecord::Migration
def self.up
create_table :my_new_table, :primary_key => :another_table_id do |t|
t.string :some_other_column
end
end
def self.down
drop_table, :my_new_table
end
end

Resources