I'm looking at the foreigner gem and trying to create some foreign keys. However, the gems documentation says that you should create your foreign keys like this `add_foreign_key(from_table, to_table, options) but when I do that, it seems like it works backwards. For example, I have 3 models, Entry, Ingredient, and an association called EntryIngredient. Entry has many Ingredients through EntryIngredient, Ingredient has many Entries through EntryIngredients, and EntryIngredient belongs to both of these. Yet this is the code that works:
class EntryIngredient < ActiveRecord::Base
belongs_to :entry
belongs_to :ingredient
end
class CreateEntryIngredients < ActiveRecord::Migration
def self.up
create_table :entry_ingredients do |t|
t.references :entry
t.references :ingredient
t.integer :quantity
t.string :unit
t.timestamps
end
add_index :entry_ingredients, [:entry_id, :ingredient_id]
add_foreign_key :entry_ingredients, :entries, :dependent => :delete
add_foreign_key :entry_ingredients, :ingredients, :dependent => :delete
end
def self.down
drop_table :entry_ingredients
end
end
By the docs I would think that the foreign key should actually be added like this:
add_foreign_key :entries, :entry_ingredients, :dependent => :delete
add_foreign_key :ingredients, :entry_ingredients, :dependent => :delete
But when I run the migration, that returns me a
Mysql2::Error: Key column 'entry_ingredient_id' doesn't exist in table: ALTER TABLE `entries` ADD CONSTRAINT `entries_entry_ingredient_id_fk` FOREIGN KEY (`entry_ingredient_id`) REFERENCES `entry_ingredient`(id) ON DELETE CASCADE
Can someone explain to me whats happening here? Why am I dyslexic?
I've verified this, if someone thinks I'm still not getting it please explain to me. But, unless my English is bad, I believe that the gem is documented incorrectly. The correct syntax is actually:
add_foreign_key(to_table, from_table, options)
So with that I would do add_foreign_key :entry_ingredients, :entries, :dependent => :delete
This logic says to me, add a foreign key to the table entry_ingredients, from table entries as entry_id, with options...
While by the documentations logic I would use this code add_foreign_key :entries, :entry_ingredients, :dependent => :delete which says to me, add a foreign key from entries (as entry_id), to entry_ingredients, with options... However, what in fact happens is we are adding a foreign key to entries, from entry_ingredients (as entry_ingredients_id). This is wrong, and not the intended result. Here is the error I receive when following the documentations method to prove it:
Mysql2::Error: Key column 'entry_ingredient_id' doesn't exist in table: ALTER TABLE `entries` ADD CONSTRAINT `entries_entry_ingredient_id_fk` FOREIGN KEY (`entry_ingredient_id`) REFERENCES `entry_ingredients`(id) ON DELETE CASCADE
I've reported this as an issue on github so hopefully he fixes the documentation.
Related
I have an active record association setup with :dependent => :destroy, which works as intended.
Then I found out I need to use delete instead of destroy due to performance, so I just changed destroy to delete_all/delete depending on the association.
When I try to delete:
shop.shop_snapshots.completed.last.delete
I get the error message:
ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR: update or delete on table "shop_snapshots" violates foreign key constraint "fk_rails_c24b24adaf" on table "inventory_items"
But why is that - I believe I have the proper setup on the snapshot:
has_many :inventory_items, :dependent => :delete_all
and it worked for destroy, so what am I doing wrong?
Thanks
/Louise
On Postgres you can use the CASCADE option on the foreign key itself.
CASCADE specifies that when a referenced row is deleted, row(s)
referencing it should be automatically deleted as well.
- https://www.postgresql.org/docs/9.5/ddl-constraints.html
This is usually setup when creating the table but you can add it to an exisiting table by removing and then re-adding the foreign key constraint:
class AddCascadeToOrderItems < ActiveRecord::Migration[6.0]
def up
remove_foreign_key :order_items, :orders
add_foreign_key :order_items, :orders, on_delete: :cascade
end
def down
remove_foreign_key :order_items, :orders
add_foreign_key :order_items, :orders
end
end
Since this is handled on the DB level no configuration is needed in your model.
has_many :inventory_items, dependent: :delete_all
Works as well and is the only option on peasant databases like MySQL but it will only be fired when you call .destroy and not .delete on the model that declares the association as its implemented as a model callback. For example:
class Store < ApplicationRecord
has_many :inventory_items, dependent: :delete_all
end
store = Store.find(1)
store.destroy # triggers callbacks and will delete all assocatiated inventory_items
store.delete # will not trigger callbacks
You need to set it on migration level.
Something like this:
create_table :childs do |t|
t.references :parent, index: true, foreign_key: {on_delete: :cascade}
t.string :name
t.timestamps null: false
end
I am using Rails 5.0.1 for web development and deploying my app on Heroku. I am using postgreSQL as my database for Heroku and sqlite3 for my local development database.
I need to link to an account_number column of an Accounts table to two columns in a Transactions table. The account_number column is not the primary key of Accounts table.
And, the Transactions table has a column from_account, which I want to link to the account_number column in Accounts table and
another column - to_account that I want to link to the same account_number column of Accounts table.
The account_number is not the primary key of accounts table but it is unique.
I am trying to do something like this in my migration file:
create_table :transactions do |t|
t.string :from_account, foreign_key: true
t.string :to_account, foreign_key: true
t.timestamps
end
add_foreign_key :transactions, column: :from_account, :accounts, column: :account_number
add_foreign_key :transactions, column: :to_account, :accounts, column: :account_number
and my model file looks like this:
class Transaction < ApplicationRecord
belongs_to :from_account, :foreign_key => 'from_account', :class_name => 'Account'
belongs_to :to_account, :foreign_key => 'to_account', :class_name => 'Account'
end
But this gives error on both my local sqlite3 database and also on the Heroku's PostgreSQL database.
How do I model something like this in Rails5.
So far all the tutorials I found online only tell how to link to the primary key of the referenced table.
EDIT: Maybe it is unclear from my question above, but the account_number field is already unique in the Accounts table in my database. This is the schema of my Accounts table:
create_table :accounts do |t|
t.string :account_number, :unique => true
# Other fields
t.timestamps
end
Try primary_key:
class Transaction < ApplicationRecord
belongs_to :from_account, :foreign_key => 'from_account', :class_name => 'Account', :primary_key => 'account_number'
belongs_to :to_account, :foreign_key => 'to_account', :class_name => 'Account', :primary_key => 'account_number'
end
As Arcana's answer says, give the referenced column via :primary_key:
class Transaction < ApplicationRecord
belongs_to :from_account, :foreign_key => 'from_account', :class_name => 'Account',
:primary_key => 'account_number'
belongs_to :to_account, :foreign_key => 'to_account', :class_name => 'Account',
:primary_key => 'account_number'
end
The language :primary_key in the belongs_to is misleading. It should really be :candidate_key or :references or :unique or primary_key_or_unique. But it isn't. It doesn't affect what the primary key in the referenced class is.
Active Record Associations
4 Detailed Association Reference
4.1 belongs_to Association Reference
4.1.2 Options for belongs_to
4.1.2.6 :primary_key
By convention, Rails assumes that the id column is used to hold the primary key of its tables. The :primary_key option allows you to specify a different column.
(In the relational model, a foreign key references a candidate key, which is some unique (not null) column set that doesn't contain a smaller unique (not null) column set, and is some set of columns you could have picked as primary key. But in (Active Record and) SQL a PRIMARY KEY actually declares a UNIQUE NOT NULL (that might contain a smaller UNIQUE NOT NULL) and a FOREIGN KEY REFERENCES a UNIQUE NOT NULL.)
I'd guess that it's caused by either not specifying a primary key during your migration or within your model
I created a many-to-many relationship in rails, here's my models and migrations
class Channel < ActiveRecord::Base
has_and_belongs_to_many :packages
validates_presence_of :name
end
class Package < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :name
end
class CreateChannelsPackages < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.references :channel
t.references :package
t.timestamps
end
add_index :channels_packages, :channel_id
add_index :channels_packages, :package_id
end
end
Then i have a multiple select, but when i try to save i get this error
SQLite3::ConstraintException: constraint failed: INSERT INTO "channels_packages" ("package_id", "channel_id") VALUES (1, 1)
I tried to remove the indexes from the migration but it didn't solve it, did somebody else have this problem?
Btw i'm using Rails 3.2.6 and sqlite3 1.3.6
I think gabrielhilal's answer is not quite correct: use of extra attributes in the join table is deprecated, thus you need to remove the timestamp in your migration, then it should work just fine with the has_and_belongs_to_many wich itself is not deprecated.
If you do need additional attributes in your join table, though, has_many :through is the way to go.
There is also another question with good answers on this topic:
Rails migration for has_and_belongs_to_many join table
I don't know if it is the reason of your problem, but the has_and_belongs_to_many association is deprecated.
According to the Rails Guide:
The use of extra attributes on the join table in a has_and_belongs_to_many association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a has_many :through association instead of has_and_belongs_to_many.
I know that you are not adding any extra attribute to the join table, but try changing your migration to the below, which I think is the default:
class CreateChannelPackageJoinTable < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.integer :channel_id
t.integer :package_id
t.timestamps
end
end
end
I'm pulling data from Harvest. Here are my two models and schema:
# schema
create_table "clients", :force => true do |t|
t.string "name"
t.integer "harvest_id"
end
create_table "projects", :force => true do |t|
t.string "name"
t.integer "client_id"
t.integer "harvest_id"
end
# Client.rb
has_many :projects, :foreign_key => 'client_id' # not needed, I know
# Project.rb
belongs_to :client, :foreign_key => 'harvest_id'
I'm trying to get the Projects to find their client by matching Project.client_id to a Client.harvest_id. Here is what I'm getting instead.
> Project.first.client_id
=> 187259
Project.first.client
=> nil
Client.find(187259).projects
=> []
Is this possible? Thanks!
Might not seem intuitive, but the foreign_key for both relations has to be the same. Let's say you decide to use harvest_id as the foreign key. It should be set up like this:
# Client.rb
has_many :projects, :foreign_key => 'harvest_id'
# Project.rb
belongs_to :client, :foreign_key => 'harvest_id'
You would also only have the harvest_id field in the projects table, since the client has_many projects.
Since your belongs_to relationship in Project model is on harvest_id, you have to ensure the harvest_id attribute is set in the project object.
> Project.first.harvest_id
=> ??
Your problem can occur if the harvest_id is not set.
Projects to find their client by matching Project.client_id to a Client.harvest_id
This does not seem to make sense as client and harvest are supposed to be different objects/records and you cannot match them.
It is like "Find apples where there are Orange-like seeds".
So we probably need more context.
You defined your relations the following way:
On the Project side you say "it is related to client via client_id", but on the Client you say that "it is related to Project via harvest_id"
There you have discrepancy.
So it seems you just have incorrect mappings defined.
Not sure how harvest_id is supposed to be used, so will make the assumption it is just association:
# Client.rb
has_many :projects
belongs_to :harvest
# Project.rb
belongs_to :client
belongs_to :harvest
# Harvest
has_one :client
has_one :project
I have the following Rails migration which works perfectly (irrelevant pieces removed):
create_table :comments do |t|
t.text :body
t.references :post
end
Now I'd like to add an author column to my comments table (which is the userid of a user), but I have no idea how to do it (I'm tempted to just write the MySql-specific syntax using an execute).
I've been looking at add_column here which doesn't mention references. I've actually found TableDefinition#references but I have no idea how to use it with an add_column statement.
Is this possible? Also, is it true that, for MySql, the "references" functionality does not actually establish relationships between the tables?
While it's too late to get any points out of this, I thought I'd post the best way for posterity :)
use change_table instead of create_table to add columns to a table that already exists, with all the TableDefinition goodness:
self.up do
change_table :comments do |t|
t.references :author
end
end
This might seem trivial, but other gems like Devise make heavy use of their own custom table definitions, and this way you can still use them.
add_reference :table_name, :reference, index: true
Finally got it
add_column :locations, :state_id , :integer, :references => "states"
First, do:
script/generate migration AddAuthorIdToComments
Open the generated file and add this line:
add_column :comments, :author_id, :integer
Then in your model files:
class User < ActiveRecord::Base
has_many :comments, :foreign_key => "author_id"
end
class Comment
belongs_to :author, :class_name => User
end
It's been a while since I've looked at this, but last I checked migrations don't support creating foreign keys. Fortunately, however, there is a plug-in for it. I've used this and it works well.
You could add the column by add_column(:table, :column_name, :type, :options) in a new Migration.