Rails: violates foreign key constraint - ruby-on-rails

I have three models: Book, genre, BookGenre, and here are relationships:
class BookGenre < ActiveRecord::Base
belongs_to :book
belongs_to :genre
end
class Book < ActiveRecord::Base
has_many :book_genres
has_many :genres, through: :book_genres
end
class Genre < ActiveRecord::Base
has_many :book_genres
has_many :books, through: :book_genres
end
And then I use seed file to put data into these tables.
But when I want to do rake db:seed again, it showed this error
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: update or delete on table "books" violates foreign key constraint "fk_rails_4a117802d7" on table "book_genres"
DETAIL: Key (id)=(10) is still referenced from table "book_genres".
In my seed.rb
Book.destroy_all
Genre.destroy_all
...create data

Add dependent: :destroy option to your has_many definitions.
Check docs
Yet better option to respect data integrity is to set the CASCADE DELETE on the database level: say, you have comments table and users table. User has many comments You want to add a foreign_key to table comments and set deleting the comment whenever the user is destroyed you would go with the following (the on_delete: :cascade option will ensure it):
add_foreign_key(
:comments,
:users,
column:
:user_id,
on_delete: :cascade
)

Try this:
ActiveRecord::Base.connection.disable_referential_integrity do
Book.destroy_all
Genre.destroy_all
# ...create data
end

Related

How to add `:dependent => :destroy` declaration on already created Rails ActiveRecord Postgresql DB table

I have a post ActiveRecord model that has_many comments, but I forgot to add :dependent => :destroy upon declaring it. Now I have several posts with comments and I cannot delete them as I get this error:
PG::ForeignKeyViolation: ERROR: update or delete on table "posts" violates foreign key constraint "fk_rails_c9b8ba77e9" on table "comments"
DETAIL: Key (id)=(2) is still referenced from table "comments".
I after the fact added the :dependent => :destroy declaration, but I am pretty sure I cannot do that, so how do I create a migration that does it?
I fixed my problem like so:
class UpdateChartForeignKey < ActiveRecord::Migration[7.0]
def change
remove_foreign_key :comments, :posts
add_foreign_key :comments, :posts, on_delete: :cascade
end
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
end
This will add the :dependent => :destroy declaration to the User model's association with the posts table, indicating that when a user is deleted, all of the associated posts should also be deleted.

How to associate table column with another table

So basically I like to know what association i need to link my routes table with employees table. I have a routes table with an employees column (array type) which holds employee id's. I also have an employee table that has (first_name, last_name, phone_number).
A has_many :employees, foreign_key: :employees, class_name: :Employee does not work and gives an error. Any ideas?
This error is given
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column employees.employees does not exist)
LINE 1: SELECT "employees".* FROM "employees" WHERE "employees"."emp...
Using an array column here is a just bad idea:
Violates first normal form.
Doesn't let you use foreign keys to maintain referential integrity.
Doesn't work with ActiveRecord assocations that expect you to model your data in a sane way.
You will need to write queries by hand.
Instead you most likely want is a self-referential assocation:
class AddManagerToEmployees < ActiveRecord::Migration[6.1]
def change
add_reference :employees, :manager,
null: true,
foreign_key: { to_table: :employees }
end
end
class Employee < ApplicationRecord
belongs_to :manager,
class_name: 'Employee'
has_many :subordinates,
class_name: 'Employee',
foreign_key: :manager_id
end
If the manager-employee relation should be many to many instead of one to many use a join table.

Naming convention for Rails models based on existing tables

I have two models. Users and Jobs. I have a join table to indicate a User's interest in a Job. I was using a has_and_belongs_to_many relationship for Users and Jobs. I now realize I need to be using a has_many :through relationship. Because of that, I need to have a model for my join table or I get undefined Constant errors. My join table is called jobs_users. What should I name the .rb file for the model and what should the class name be in order to get Rails magic to work? I have found SO questions that imply the class should be JobUser, but nothing on what the .rb file should be named.
Naming convention for Rails models based on existing tables
Model class names use CamelCase. These are singular, and will map automatically to the plural database table name.
> 'JobsUser'.underscore
=> "jobs_user"
This means your file should be named jobs_user.rb. And class will look like below
class JobsUser < ApplicationRecord
...........
end
For fresh migration, you can rollback and destroy current table migration e.g jobs_users and then run this command
rails g model UserJob user:references job:references
db/migrate/TIMESTAMP_create_user_jobs.rb file looks like below
class CreateUserJobs < ActiveRecord::Migration[5.1]
def change
create_table :user_jobs do |t|
t.references :user, foreign_key: true
t.references :job, foreign_key: true
t.timestamps
end
end
end
then migrate this and created model file name is user_job.rb
has_many :through relationship will looks like this
#user.rb
class User < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :jobs, through: :user_jobs
end
#job.rb
class Job < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :users, through: :user_jobs
end
#user_job.rb
class UserJob < ApplicationRecord
belongs_to :user
belongs_to :job
end
That's it

How to delete Active Record object with a foreign key without using cascading deletes?

I have two models, LaserSheet and Item, that relate to each other with a has_many relationship:
class LaserSheet < ActiveRecord::Base
belongs_to :job
has_many :items
...
end
class Item < ActiveRecord::Base
belongs_to :job
has_many :laser_sheets
...
end
Because they have a many-to-many relationship, I want to be able to delete an Item without deleting its associated LaserSheets, and similarly delete a LaserSheet without deleting its associated Items. However, when I attempt to delete one of the objects I get a Foreign Key error:
ERROR: update or delete on table "items" violates foreign key constraint "fk_rails_f7f551ebf9" on table "laser_sheets" DETAIL: Key (id)=(293) is still referenced from table "laser_sheets".
EDIT:
DB migrations adding refs between the two models:
class AddItemRefToLaserSheets < ActiveRecord::Migration
def change
add_reference :laser_sheets, :item
end
end
class AddLaserSheetRefToItems < ActiveRecord::Migration
def change
add_reference :items, :laser_sheet
end
end
Check out the dependent options. You may want something like:
class LaserSheet < ActiveRecord::Base
has_many :items, dependent: :nullify
...
class Item < ActiveRecord::Base
has_many :laser_sheets, dependent: :nullify
...

What is the behaviour of create_join_table of ActiveRecord::Migration?

I've discovered a nice way to generate join tables for my HABTM relationships in my Rails app.
rails g migration CreateJoinTable table1 table2
This generates an ActiveRecord::Migration that employs the method create_join_table
I'm wondering what this wonderful mysterious method does. I guess it makes a table (probably without an id field) that has a column for table1 foreign key and a column for table2 foreign key, but does the table have any other features?. My habit for join tables has always been to add a unique index across both those columns so that a relationship between a record in table1 and a record in table2 cannot be entered twice.
My question boils down to: If I use create_join_table do I need to keep adding that unique index, or does this method do that for me (I think it should)?
The documentation I usually look at doesn't go into this sort of detail.
Called without any block, create_join_table just creates a table with two foreign keys referring to the two joined tables.
However, you can actually pass a block when you call the method to do any additional operations (say, adding indexes for example). From the Rails doc:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
Have a look at create_join_table documentation.
You can check the create_join_table code at the bottom (click on Source: show).
SchemaStatements#create_join_table() only creates join table without any fancy indexes etc,... So if you wish to use uniqueness constraint on two fields you have to do something like this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users do |t|
t.integer :post_id, index: true
t.integer :user_id, index: true
t.index [:post_id, :user_id], name: 'post_user_un', unique: true
end
end
end
Please also note that create_join_table by default does NOT create id field.
It turns out it doesn't do any more than the basics I described in the question. I found this out simply by running the migration and seeing what ends up in db/schema.rb
For those interested, to get the unique index do this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users
add_index :posts_users, [:post_id, :user_id], unique: true, name: 'index_posts_users'
end
end
Also be aware of how you define the dependent destroy for this join table.
If you later move away from HABTM and define the relationships using through: and get it wrong you might run into the 'to_sym' error I reported here.
Make sure you have defined the destroy like this:
class Proposal < ActiveRecord::Base
has_many :assignments
has_many :products, through: :assignments, dependent: :destroy # <- HERE
end
class Product < ActiveRecord::Base
has_many :assignments
has_many :proposals, through: :assignments, dependent: :destroy # <- HERE
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end
not this:
class Proposal < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :products, through: :assignments
end
class Product < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :proposals, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end

Resources