Rails: remove reference relationship - ruby-on-rails

I was confused with following code in tutorial.
The goal is to remove reference key genre_id from table books
class RemoveGenreFromBooks < ActiveRecord::Migration
def up
remove_index :books, column: [:genre_id]
remove_column :books, :genre_id
end
def down
add_reference :books, :genre, index: true
end
end
But I don't understand what remove_index :books, column: [:genre_id] mean
Furthermore, I didn't get that index: true in down method.
If I need to add a relationship, why I can not just type
class Addrelationship < ActiveRecord::Migration
def change
add_reference :books, :genre
end

As there is a method to add a referecen, there is a one to remove also - remove_reference
Syntax is: remove_reference(table_name, ref_name, options = {})
So in your case, to remove the reference of Genre :
class RemoveGenreFromBooks < ActiveRecord::Migration
def change
remove_reference :books, :genre, index:true, foreign_key: true
end
end
The option foreign_key: true will also remove the foreign key from the books table.

rake db:rollback STEP=1
Is a way to do this, if the migration you want to rollback is the last one applied. You can substitute 1 for however many migrations you want to go back.

Related

Rails self-join migration

I'm trying to use the Amoeba gem to create drafts by duplicating my Model. What I would like to do is create a self-join, such that:
mainRecord.draft gives the draft, and
draftRecord.master gives the master record.
The code should be simple enough:
class AddMasterToOrganizations < ActiveRecord::Migration[6.1]
def change
add_reference :organizations, :master, foreign_key: { to_table: :organizations }
end
end
With strong_migrations, however, it wants me to add the index concurrently, like so:
class AddMasterToOrganizations < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def change
add_reference :organizations, :master, index: {algorithm: :concurrently}
end
end
If I do that, I'm not sure what other migration to create to get the foreign key working properly.

Rails rename_column changing the column's type

I'm writing a down method in my migration to deal with reverting a migration that replaces a string field with a reference field. Here's the code:
class ChangeCars < ActiveRecord::Migration[5.1]
def up
rename_column :cars, :make_id, :make_id_string
add_reference :cars, :make, foreign_key: true, optional: true
Cars.all.each do |car|
unless car.make_id_string.nil?
make = Make.find_by(uuid: car.uuid)
car.make_id = make
end
car.save!
end
remove_column :cars, :make_id_string
end
def down
add_column :cars, :make_id_string, :string
Cars.all.each do |car|
unless car.make.nil?
car.make_id_string = car.make.uuid
end
car.save!
end
# at this point :make_id_string -> String
remove_reference :cars, :make, index: true, foreign_key: true
rename_column :cars, :make_id_string, :make_id
# at this point :make_id -> Fixnum
end
end
It seems like when I'm removing the reference, I'm not completely flushing it out so when I replace the make_id field, it takes on that fixnum type.
Any suggestions are appreciated!
If you have real data in the make_id_string I would strongly suggest not to remove it. If you have some bugs in the migration, it would save you. Also reverting the migration would be far easier.
If you want to iterate over all models in Car, don't use #each, because if loads all the cars to memory at once. Use #find_each that loads records in batches.

Rails migration with add_foreign_key: 'column "user_id" referenced in foreign key constraint does not exist'

(Rails is version 5.0.0, Ruby 2.3.0p0)
I want to create an association between my Users table and Cards table. I've added belongs_to :user to the Cards model, and has_many :cards to the Users model, and created a migration with:
class AddUserIdToCard < ActiveRecord::Migration[5.0]
def change
add_foreign_key :cards, :users, column: :user_id
end
end
When I run rake db:migrate, I receive the error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "user_id" referenced in foreign key constraint does not exist
: ALTER TABLE "cards" ADD CONSTRAINT "fk_rails_8ef7749967"
FOREIGN KEY ("user_id")
REFERENCES "users" ("id")
Now I initially worked around this problem simply by adding add_column :cards, :user_id, :integer to the migration, but that doesn't really seem very tidy, and I'm worried about problems coming up later. Is there a better way to accomplish this?
You're setting a foreign key for cards table with the column user_id. But you haven't created a reference yet. Create a reference and then add foreign key to maintain referential integrity. Rollback and modify your migration with
1 class AddUserIdToCard < ActiveRecord::Migration[5.0]
2 def change
3 add_reference :cards, :users, index:true
4 add_foreign_key :cards, :users
5 end
6 end
Line 3 will create, in the cards table, a reference to id in the users table (by creating a user_id column in cards).
Line 4 will add a foreign key constraint to user_id at the database level.
For more, read Add a reference column migration in Rails 4
The answer provided is not accurate for Rails 5. Scroll to the add_reference bit of the docs for more, but in the case of the above question, you would use:
class AddUserIdToCard < ActiveRecord::Migration[5.0]
def change
add_reference :cards, :users, foreign_key: true
end
end
In rails 6, I believe this is now
def change
add_column :cards, :user_id, :integer, index: true
add_foreign_key :cards, :users
end
class AddUserIdToCard < ActiveRecord::Migration[5.2]
def change
add_foreign_key :cards, :users, column: :user_id, primary_key: :"id", on_delete: :cascade
end
end
Try this migration. I was facing the same problem then I fixed it.

Migration for changing belongs_to association

I have a model called categories currently they belong to product but I'd like them to belong to store instead. I have several thousand of these so what I'd like to do is create a migration that adds a store_id to categories and then, gets the associated product.store.id from it's current association and adds that to the store_id. After that I'd like to remove the product association.
Does anybody know how to easily and safely achieve that?
Should you add the association in the wrong direction, you can use change_table to reverse the association:
class CreateFavorites < ActiveRecord::Migration[5.0]
def change
create_table :favorites do |t|
t.belongs_to :article
t.timestamps
end
end
end
class RemoveArticleFromFavorites < ActiveRecord::Migration[5.0]
def change
change_table :favorites do |t|
t.remove_references :article
end
end
end
class AddFavoriteToArticles < ActiveRecord::Migration[5.0]
def change
change_table :article do |t|
t.belongs_to :favorite
end
end
end
First rename column to store_id,
rename_column :categories, :product_id, :store_id
Then change the assosciation.
Now you can either write a rake task to transfer the data or you can manually do it via console.
It's better way to write a rake task.
According to your requirement your rake task can be, get the store from the product and assign to the category according to your requirement.
require 'rake'
namespace :category do
task :product_to_store => :environment do
Category.all.each do |category|
product = Product.find(category.store_id) //you will get product,as now it chnaged to store_id
if product.present?
category.store_id = product.try(:store).try(:id) //assign the product's store_id to the category, use try to reject errored records
category.save
end
end
end
end
Now run, **`rake category:product_to_store`**, thats it, the data gets transfered.
You can just add new migration that will create the new reference with categories as store.
class YourMigrationName < ActiveRecord::Migration
def up
add_reference :categories, :store, index: true
Category.all.each do |category|
category.store_id = category.product_id
category.save
end
remove_column :product_id
end
def down
add_reference :categories, :product, index: true
Category.all.each do |category|
category.product_id = category.store_id
category.save
end
remove_reference :categories, :store, index: true
end
end
May be if you have added the product reference and index then write the same as the store so then it will delete the index as well.
you have data in the column you don't want to lose, then use rename_column

Rails Migration: add_reference to Table but Different Column Name For Foreign Key Than Rails Convention

I have the following two Models:
class Store < ActiveRecord::Base
belongs_to :person
end
class Person < ActiveRecord::Base
has_one :store
end
Here is the issue: I am trying to create a migration to create the foreign key within the people table. However, the column referring to the foreign key of Store is not named store_id as would be rails convention but is instead named foo_bar_store_id.
If I was following the rails convention I would do the migration like this:
class AddReferencesToPeople < ActiveRecord::Migration
def change
add_reference :people, :store, index: true
end
end
However this will not work because the column name is not store_id but is foo_bar_store_id. So how do I specify that the foreign key name is just different, but still maintain index: true to maintain fast performance?
in rails 5.x you can add a foreign key to a table with a different name like this:
class AddFooBarStoreToPeople < ActiveRecord::Migration[5.0]
def change
add_reference :people, :foo_bar_store, foreign_key: { to_table: :stores }
end
end
Inside a create_table block
t.references :feature, foreign_key: {to_table: :product_features}
In Rails 4.2, you can also set up the model or migration with a custom foreign key name. In your example, the migration would be:
class AddReferencesToPeople < ActiveRecord::Migration
def change
add_column :people, :foo_bar_store_id, :integer, index: true
add_foreign_key :people, :stores, column: :foo_bar_store_id
end
end
Here is an interesting blog post on this topic. Here is the semi-cryptic section in the Rails Guides. The blog post definitely helped me.
As for associations, explicitly state the foreign key or class name like this (I think your original associations were switched as the 'belongs_to' goes in the class with the foreign key):
class Store < ActiveRecord::Base
has_one :person, foreign_key: :foo_bar_store_id
end
class Person < ActiveRecord::Base
belongs_to :foo_bar_store, class_name: 'Store'
end
Note that the class_name item must be a string. The foreign_key item can be either a string or symbol. This essentially allows you to access the nifty ActiveRecord shortcuts with your semantically-named associations, like so:
person = Person.first
person.foo_bar_store
# returns the instance of store equal to person's foo_bar_store_id
See more about the association options in the documentation for belongs_to and has_one.
To expand on schpet's answer, this works in a create_table Rails 5 migration directive like so:
create_table :chapter do |t|
t.references :novel, foreign_key: {to_table: :books}
t.timestamps
end
EDIT: For those that see the tick and don't continue reading!
While this answer achieves the goal of having an unconventional foreign key column name, with indexing, it does not add a fk constraint to the database. See the other answers for more appropriate solutions using add_foreign_key and/or 'add_reference'.
Note: ALWAYS look at the other answers, the accepted one is not always the best!
Original answer:
In your AddReferencesToPeople migration you can manually add the field and index using:
add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
And then let your model know the foreign key like so:
class Person < ActiveRecord::Base
has_one :store, foreign_key: 'foo_bar_store_id'
end
# Migration
change_table :people do |t|
t.references :foo_bar_store, references: :store #-> foo_bar_store_id
end
# Model
# app/models/person.rb
class Person < ActiveRecord::Base
has_one :foo_bar_store, class_name: "Store"
end
Under the covers add_reference is just delegating to add_column and add_index so you just need to take care of it yourself:
add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
foreign key with different column name
add_reference(:products, :supplier, foreign_key: { to_table: :firms })
refer the documentation Docs

Resources