Adding a reference to a non-integer column in rails migration - ruby-on-rails

I have studied several questions on this topic today. I know that, I can use t.references in migration to add a reference. But, If a table has non-integer primary key, how do I add reference to that column?
I have a table with this definition
create_table :sessions, id: false do |t|
t.string :session, primary_key: true, limit: 10
t.timestamps null: false
end
How do I add a reference to the session column (the name doesn't matter here), which is a string from another table migration. I tested with t.references but that just add an integer column. I know that I can use add column. But how to do that without from create_table method directly?
Clarification for duplicate flag
This question is flagged as duplication of this question, but it is actually not. Because I am not asking about setting up table with non-default non-integer primary key, because I already set up that table. I am asking about referencing this type of table from another table.

Rather than using the references helper, you would need to use the string type, and add an index for faster lookups. If your other model is "Record":
rails generate migration AddSessionIdToRecords session:string:index
Which should generate a migration like so:
def change
add_column :records, :session_id, :string
add_index :records, :session_id
end
Since you're using unconventional naming, you'd need to specify the primary key in the model and the relation definition:
class Session < ActiveRecord::Base
self.primary_key = "session"
has_many :records, primary_key: "session"
end
class Record < ActiveRecord::Base
belongs_to :session, primary_key: "session"
end

Since rails version 4.2.7 You can specify type into the add_reference helper.
Suppose you have table records and you want to have reference of sessions inside records. Then you can do the following :
add_reference :records, :sessions, type: :string, foreign_key: {to_table: :sessions, primary_key: :session}

Actually, you don't have to add primary_key in class Session & Record
def change
add_column :records, :session_id, :string
add_index :records, :session_id
add_foreign_key :records, :sessions, column: :session_id, primary_key: "your_primary_key_name_in_sessions_table"
end
and that will be all.

I've arrived at the conclusion that, without using foreign_key which is introduced in rails 4.2, I can't truly reference another column, only creating index as suggested by this answer from eirikir.
From rails 4.2, I can use foreign key like this
In session Table migration
create table :sessions, id: false do |t|
t.string "session", primary_key: true
t.timestamp null: false
end
For a record table to reference the session primary key
create table :records do |t|
t.string "session", null: false
..
.. # other attributes
end
add_index :records, :session
add_foreign_key :records, :sessions, column: :session, primary_key: :session
Here, the column: option fixes, on which column i want to create the foreign key and the primary_key: option fixes, the primary key of the referenced table, which is here session.

Related

how do rails associations get set?

I always seem to have trouble with this concept. I get what associations allow you to do, I just never seem to be able to tell if the associations are set in an application.
For example, I generated a scaffold for line_items and before I ran my migration, I set the belongs_to and has_many methods in the correct models, and then ran my migration.
After running my migration, I look at my schema and I can't tell if there are any associations set. To me it does not seem like it because I don't see the schema setting any relationships.
Do the has_many and belongs_to methods actually set the association? Or are they there for developers reading the code to understand the relationship?
How would my schema look if the associations were set properly? Do I need to rollback my last migration and include the correct indexes?
create_table "carts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "line_items", force: :cascade do |t|
t.integer "product_id"
t.integer "cart_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "products", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "image_url"
t.decimal "price", precision: 8, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
it's very simple to tell if the association is set up properly or not through your schema. basically the model has belongs_to should have the corresponding table that contains the foreign_id. For example in the schema you post, it's easy to see that the lineItem belongs to 'Product' and Cart, because it has cart_id and product_id(two foreign id).
activerecord is not a black magic, all has_many and belongs_to do is just dynamically adds a method to the model class, which translate the query to raw sql for you and map the result to ruby object. however it is your responsibility to set up the database table correctly. Because after all, activerecord use SQL to query data following rails convention.
update
I think what you mean is the helper method in your migration file such as
t.belongs_to product, index: true, foreign_key: true
this line of code is also not a black magic, it is a helper method rails provide to make your life much easier. It is equivalent to do three simple things to create your database table.
create a foreign_id depends on your input, in the above example, it will be product_id. equivalent to t.integer product_id
add an index on the foreign_id, because usually you query out the associated data a lot by using the foreign_id, add an index will improve your efficiency. equivalent to add_index "xxx", ["product_id"], name: "index_xxxs_on_product_id"
at the end the foreign_key: true is going to set up an database constrains for your foreign_id, so that if there is still one row in your child table related to one row in your parent table, the row in your parent table will not be accidentally deleted. this is equivalent to add_foreign_key :xxxs, :products
so as a conclusion, using add_foreign_key :articles, :authors will not make anything look special in your schema, because ActiveRecord is just a translator to make your life easier when you deal with sql database, it can only do the thing sql database can do, not anything special. the idea of association in database is just saving a foreign_id in one table so that you can query the related data in another table by using the foreign_id.
The "association" is a rails concept that is implemented by has_many and belongs_to... so those lines are not just for documentation, they create the association.
You have product_id and cart_id in the line_items table and I assume
class LineItem
belongs_to :cart
belongs_to :product
...
end
class Product
has_many :line_items
...
end
class Cart
has_many :line_items
...
end
The has_many and belongs_to do set the associations for rails and means rails now knows that there are the associations...
my_line_item.cart
my_line_item.product
my_cart.line_items
my_product.line_items
If you didn't have the has_many and belongs_to it wouldn't work.
The columns card_id and product_id are needed for the associations to work as they're the way that records are linked and need to be present on the belongs_to side of the relationship. They don't have to be called what they're called but if you don't specifically use a different foreign_key name in the belongs_to and has_many then these field names are what will be expected by rails. And it's best to use the expected names... "Convention over configuration" is the preference.
If you did decide to call the foreign key vegetable_id instead of product_id that's fine, but then you'd define the association as
class LineItem
belongs_to :product, foreign_key: :vegetable_id
and
class Product
has_many :line_items, foreign_key: :vegetable_id
You can go an extra step and have...
class Cart
has_many :line_items
has_many :products, through: :line_items
...
end
This auto-magically gives you the ability to do..
my_cart.products
As rails knows how to build the SQL command to get all products for a cart via the line_items.
Because you need the foreign key in the belongs_to table, you would need to specify that foreign key when you create the table.
create_table :line_items do |t|
t.integer :product_id
There is a helper_method called references (also aliased as belongs_to) which basically does the same thing... creates the :product_id field...
create_table :line_items do |t|
t.belongs_to :product
or
create_table :line_items do |t|
t.references :product
The three versions above basically do the same thing... they create the integer column :product_id
You can also index on the :product_id field to improve retrieval performance, so you'll occastionally see index: true but this isn't essential.

rails database constraint for self referential association

I'm building a rails website, which involves a directed friendship relation. I know in model level, it is a self referential association. And there are methods like has_and_belongs_to for that association.
My question is: how can I set up the database level constraints for this relation. I guess the migration would be something like this, which uses foreign keys to guarantee the referential integrity:
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
t.integer :accepted, null: false, default: 0
end
end
But when I run rake db:migrate, it has error:
PG::DuplicateObject: ERROR: constraint "fk_friendships_user_id" for relation "friendships" already exists
As a matter of fact, I'm not even sure whether it is necessary for me to set up the database constraint in this case, since I've seen some people's implementation of friendship relation has no database constraint like this:
create_table :friendships do |t|
t.integer :user_id
t.integer :friend_id
t.timestamps
end
According to Rails Guide
The Active Record way claims that intelligence belongs in your models, not in the database. As such, features such as triggers or constraints, which push some of that intelligence back into the database, are not heavily used.
I'm not sure whether in this case, the database constraints are heavily used.
So is it really necessary for me to set up database level constraints (using foreign keys) in this case? Or I just need to realize the constraints in model level? Thanks!!
You have declared user relation twice:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
Seems that it should be like this:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :friend, null: false, foreign_key: true
To answer your question: how can I set up the database level constraints for this relation?
Answer: Just like you already have.
Often developers go the rails way and set these constraints in model, but it's perfectly reasonable to set them up in database.
EDIT:
This will let you create a table with friend_id
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.integer :friend_id, null: false
t.integer :accepted, null: false, default: 0
end
add_foreign_key :friendships, :users, column: :friend_id
end
end
I think you're getting confused about the role of foreign_keys in your database architecture.
ActiveRecord is just a "coating" for SQL.
It's able to form queries etc which allow you to build associated objects, thus the most important thing you can do is associate those objects properly.
The way to do this - in SQL - is to use a foreign_key, which essentially shows the likes of ActiveRecord (and SQL if you use a join query) which data is associated:
Foreign keys are a standard element of relational database structures, which you probably know.
The reason why your data structure is failing is due to the fact you've replicated the user_id foreign key in your friendships table.
You'll want to refer to the following:
Rails: self join scheme with has_and_belongs_to_many?
This shows you that if you want to create a self referential join table (such as you're doing), you need to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
#db/migrate/______.rb
class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
def self.down
remove_index(:friendships, [:friend_user_id, :user_id])
remove_index(:friendships, [:user_id, :friend_user_id])
drop_table :friendships
end
end
Notice how the references are for user_id and friend_user_id?
These are the two foreign keys you need to make sure your has_and_belongs_to_many is able to associate two objects of the same model.

How to migrate from using one foreign key to another in ActiveRecord?

Using Postgres as the backing store, I have a table which (at least for the time being) has both an integer primary key and a uuid with a unique index.
It looks something like this in my schema.rb (simplified for example):
create_table "regions", force: cascade do |t|
t.integer "region_id"
t.uuid "uuid", default: "uuid_generate_v4()"
t.string "name"
end
add_index "regions", ["uuid"], name "index_regions_on_uuid", unique: true, using :btree
I then have a table which has a reference to the integer id, something like this:
create_table "sites", force:cascade do
t.integer "site_id"
t.integer "region_id"
t.string "name"
end
What I want to do is to switch from region_id to uuid as the foreign key in the second table. How should I god about writing this migration?
Just create a migration, and inhale some SQL magic into it:
def up
# Create and fill in region_uuid column,
# joining records via still existing region_id column
add_column :sites, :region_uuid
if Site.reflect_on_association(:region).foreign_key == 'region_id'
# We won't use 'joins(:regions)' in case we will need
# to re-run migration later, when we already changed association
# code as suggested below. Specifying join manually instead.
Site.joins("INNER JOIN regions ON site.region_id = regions.id").update_all("region_uuid = regions.uuid")
end
drop_column :sites, :region_id
end
Then you just need to fix your association:
class Site < ActiveRecord::Base
belongs_to :region, primary_key: :uuid, foreign_key: :region_uuid
end
class Region < ActiveRecord::Base
has_many :sites, primary_key: :uuid, foreign_key: :region_uuid
end
From your comment, its seems that you want to modify the primary key referenced by the association, not the foreign key. You actually don't need a migration to do this. Instead, just specify the primary key on the association definitions in each model:
Class Region << ActiveRecord::Base
has_many :sites, primary_key: :uuid
end
Class Site << ActiveRecord::Base
belongs_to :region, primary_key: :uuid
end
The foreign key, since it follows rails convention of being named as the belongs_to relation with an appended "_id" (in this case, region_id), does not need to be specified here.
ETA: You will also need to ensure that the type of sites.region_id matches the type of regions.uuid, which I assume is uuid. I'm also going to assume that this field was previously indexed (under ActiveRecord convention) and that you still want it indexed. You can change all this in a migration like so:
def up
remove_index :sites, :region_id
change_column :sites, :region_id, :uuid
add_index :sites, :region_id
end
def down
remove_index :sites, :region_id
change_column :sites, :region_id, :integer
add_index :sites, :region_id
end

Rails reference a model with a non integer primary key

I have a table whose migration is this:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :primary_key => :tag do |t|
t.string :name
end
change_column :clients, :tag, :string, limit: 4
end
end
so the primary key as you can see is that tag field which is a varchar(4), now I need to reference that field from another table.
I tried in the migration to create the reference in this way:
t.references :client, index: true
but the resulting table has a field called client_id which is int, should I pass any parameter to the references method so that it will create the field as varchar?
Thank you in advance
I believe you can use execute to force rails into setting the primary key in the way you want:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :id => false do |t|
t.string :name
end
execute "ALTER TABLE clients ADD PRIMARY KEY (name);"
And then set do set_primary_key :name in your Client model

Do we always use reference type for foreign keys?

As the title says, do we always use type reference for foreign keys?
No you don't have to.
As stated in the Rails guides:
http://guides.rubyonrails.org/migrations.html#special-helpers
Another helper is called references (also available as belongs_to). In
its simplest form it just adds some readability.
Another option, in a migration you could just declare:
t.integer :account_id # where :account_id will hold the id being referenced to for a belongs_to
So for example. The rails guides examples is this:
create_table :products do |t|
t.references :category
end
but you could also do this:
create_table :products do |t|
t.integer :category_id
end

Resources