In my rails app I have 2 models Profile and Skill.
Profile has_and_belongs_to_many Skill and can only have one time the same Skill.
Skill has_and_belongs_to_many Profile. If we respect the first relation, it should therefore not have more than once the same Profile.
When I create my join table I have two possibilities:
rails g migration CreateProfilesSkillsJoinTable profiles:uniq skills
or
rails g migration CreateProfilesSkillsJoinTable profiles skills:uniq
The first option will generate
class CreateProfilesSkillsJoinTable < ActiveRecord::Migration[5.1]
def change
create_join_table :profiles, :skills do |t|
t.index [:profile_id, :skill_id], unique: true
# t.index [:skill_id, :profile_id]
end
end
end
The second will generate
class CreateProfilesSkillsJoinTable < ActiveRecord::Migration[5.1]
def change
create_join_table :profiles, :skills do |t|
# t.index [:profile_id, :skill_id]
t.index [:skill_id, :profile_id], unique: true
end
end
end
You want to make the index unique :
add_index :something, [:profile_id, :skill_id], unique: true
First first rule is verified (you can get 1:2 only once). Note that even with an habtm, you'll tend to create your relation the same way (profile.skills << skill), you just need to ensure skill.profiles << profile does not creates unwanted relations
Related
So I'm trying to create a join table between the tables users and looking_for_options.
This is my migration file:
class CreateJoinTableOptionsUsers < ActiveRecord::Migration[5.0]
def change
create_join_table :looking_for_options, :users do |t|
t.index [:looking_for_option_id, :user_id]
t.index [:user_id, :looking_for_option_id]
end
end
end
But I'm getting this error:
Index name 'index_looking_for_options_users_on_looking_for_option_id_and_user_id' on table 'looking_for_options_users' is too long; the lim
it is 64 characters
Knowing that for a join table, rails convention is Table_A_Name_Table_B_Name and its columns follow a similar convention Table_A_id and Table_B_id.
How do I specify a shorter column name for the joint table so it doesn't break rails many-to-many associations?
Update:
I found that I can give just the index a different name instead. But will rails's many-to-many association actually utilize it?
class CreateJoinTableOptionsUsers < ActiveRecord::Migration[5.0]
def change
create_join_table :looking_for_options, :users do |t|
t.index [:looking_for_option_id, :user_id], name: 'option_user'
t.index [:user_id, :looking_for_option_id], name: 'user_option'
end
end
end
... will rails's many-to-many association actually utilize it?
The choice of whether to use the index or not is made by the database optimiser, and is not affected by Rails. You can name it what you like, within the constraints imposed by the database.
Goal: A user can create a User account (devise), and subsequently a Group. Each User only belongs_to one group and a group has_many Users.
After creating and running the Migrations - If I attempt to create a User i’m being presented with the following error: “1 error prohibited this user from being saved: Group must exist”.
Clearly the current setup wants a group_id to exist when creating a user.
Is a belongs_to / has_many association correct for this situation? Should this be a has_one?
Should both migrations have a foreign key attribute?
Is setting #group.user_id = current_user.id in GroupsController#create a suitable way to assign the creating user to the group? I tried to do this in the Groups model, by using a callback but I wasn’t able to access the current_user variable.
I would also like to enforce (at the database level) that a user can only belong to one group - Is this achieved using unique => true in the schema?
How can I enforce (at the database level) that a group must have a user?
.
class Group < ApplicationRecord
has_many :users
validates :users, presence: true
end
class User < ApplicationRecord
...
belongs_to :group
...
end
class GroupsController < ApplicationController
...
def create
#group = Group.new(group_params)
#group.user_id = current_user.id
...
end
...
private
...
def group_params
params.require(:group).permit(:name, :user_id)
end
...
end
class AddGroupReferenceToUser < ActiveRecord::Migration[5.0]
def change
add_reference :users, :group, foreign_key: true
end
end
class AddUserReferenceToGroup < ActiveRecord::Migration[5.0]
def change
add_reference :groups, :user, foreign_key: true
end
end
ActiveRecord::Schema.define(version: 20160903125553) do
create_table "groups", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["user_id"], name: "index_groups_on_user_id"
end
create_table "users", force: :cascade do |t|
...
t.integer "group_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["group_id"], name: "index_users_on_group_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
Is a belongs_to / has_many association correct for this situation? Should this be a has_one?
To setup a relationship where a user can only belong to a single group but groups have many users then you are on the right track.
class Group < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :group
end
belongs_to places the foreign key column as users.group_id. Using has_one would place it on groups.user_id column which would only allow a one to one mapping - a group could only have a single user.
Should both migrations have a foreign key attribute?
No - only the the users table should contain a foreign key. The AddUserReferenceToGroup should be removed or you should write another migration which removes the groups.user_id column if you have pushed it to production.
Is setting #group.user_id = current_user.id in GroupsController#create a suitable way to assign the creating user
to the group?
No - since the group_id column is on the users column you need to update the users table - not groups.
if #group.save
current_user.update(group: #group)
end
I would also like to enforce (at the database level) that a user can only belong to one group - Is this achieved using unique => true in the schema?
No - since a row on the users table can only have one id in the groups_id column a user can only belong to one group anyways. Using unique => true would create a uniqueness index on the users.groups_id column which would only allow a single users to be associated with a group.
How can I enforce (at the database level) that a group must have a user?
This is not actually possible. To be able to associate a user with a group you must first insert the group into the database so that it is assigned a id.
Adding a validation on the software level would also create a "chicken vs egg" situation where a group cannot be valid since it has no users and a user cannot be associated with a group since it is not persisted.
You can however setup a constraint on the belongs_to end by declaring the foreign key column as NOT NULL.
I would like to add a has_many relationship to two existing tables/models in my app & I'm not too sure how to di it?
When I did this before with a new model the rails generate command handled everything for me, with just rails generate model Photo image:string hikingtrail:references it created the below migration
class CreatePhotos < ActiveRecord::Migration
def change
create_table :photos do |t|
t.string :image
t.references :hikingtrail
t.timestamps
end
add_index :photos, :hikingtrail_id
end
end
Now I would like set up a relationship between users & photos with each user has_many :photos.
When I generate a migration to achieve this it does not include the add_index :photos, :user_id, is this something I should be doing manually or are the below steps enough for setting up this relationship in my database?
rails g migration AddUserIdToPhotos user_id:integer
which creates...
class AddUserIdToPhotos < ActiveRecord::Migration
def change
add_column :photos, :user_id, :integer
end
end
& then run...
rake db:migrate
It is enough to set up your relationship. You can add a index to improve the speed of your record searching. In fact some recommend to put a index to all the foreign keys. But don't worry about this now, i guess you are not going to have that many records to use a index.
If you have already migrated everything and want to add a index make do:
rails g migration AddIndexToUserIdToPhotos
and inside add the index column:
class AddUserIdToPhotos < ActiveRecord::Migration
def change
add_index :photos, :user_id
end
end
I have two models restaurant and user that I want to perform a has_and_belongs_to_many relationship.
I have already gone into the model files and added the has_and_belongs_to_many :restaurants and has_and_belongs_to_many :users
I assume at this point I should be able to do something like with Rails 3:
rails generate migration ....
but everything I have tried seems to fail. I'm sure this is something really simple I'm new to rails so I'm still learning.
You need to add a separate join table with only a restaurant_id and user_id (no primary key), in alphabetical order.
First run your migrations, then edit the generated migration file.
Rails 3
rails g migration create_restaurants_users_table
Rails 4:
rails g migration create_restaurants_users
Rails 5
rails g migration CreateJoinTableRestaurantUser restaurants users
From the docs:
There is also a generator which will produce join tables if JoinTable
is part of the name:
Your migration file (note the :id => false; it's what prevents the creation of a primary key):
Rails 3
class CreateRestaurantsUsers < ActiveRecord::Migration
def self.up
create_table :restaurants_users, :id => false do |t|
t.references :restaurant
t.references :user
end
add_index :restaurants_users, [:restaurant_id, :user_id]
add_index :restaurants_users, :user_id
end
def self.down
drop_table :restaurants_users
end
end
Rails 4
class CreateRestaurantsUsers < ActiveRecord::Migration
def change
create_table :restaurants_users, id: false do |t|
t.belongs_to :restaurant
t.belongs_to :user
end
end
end
t.belongs_to will automatically create the necessary indices. def change will auto detect a forward or rollback migration, no need for up/down.
Rails 5
create_join_table :restaurants, :users do |t|
t.index [:restaurant_id, :user_id]
end
Note: There is also an option for a custom table name that can be passed as a parameter to create_join_table called table_name. From the docs
By default, the name of the join table comes from the union of the
first two arguments provided to create_join_table, in alphabetical
order. To customize the name of the table, provide a :table_name
option:
The answers here are quite dated. As of Rails 4.0.2, your migrations make use of create_join_table.
To create the migration, run:
rails g migration CreateJoinTableRestaurantsUsers restaurant user
This will generate the following:
class CreateJoinTableRestaurantsUsers < ActiveRecord::Migration
def change
create_join_table :restaurants, :users do |t|
# t.index [:restaurant_id, :user_id]
# t.index [:user_id, :restaurant_id]
end
end
end
If you want to index these columns, uncomment the respective lines and you're good to go!
When creating the join table, pay careful attention to the requirement that the two tables need to be listed in alphabetical order in the migration name/class. This can easily bite you if your model names are similar, e.g. "abc" and "abb". If you were to run
rails g migration create_abc_abb_table
Your relations will not work as expected. You must use
rails g migration create_abb_abc_table
instead.
For HABTM relationships, you need to create a join table. There is only join table and that table should not have an id column. Try this migration.
def self.up
create_table :restaurants_users, :id => false do |t|
t.integer :restaurant_id
t.integer :user_id
end
end
def self.down
drop_table :restaurants_users
end
You must check this relationship rails guide tutorials
What I need is a migration to apply unique constraint to a combination of columns. i.e. for a people table, a combination of first_name, last_Name and Dob should be unique.
add_index :people, [:firstname, :lastname, :dob], unique: true
According to howmanyofme.com, "There are 46,427 people named John Smith" in the United States alone. That's about 127 years of days. As this is well over the average lifespan of a human being, this means that a DOB clash is mathematically certain.
All I'm saying is that that particular combination of unique fields could lead to extreme user/customer frustration in future.
Consider something that's actually unique, like a national identification number, if appropriate.
(I realise I'm very late to the party with this one, but it could help future readers.)
You may want to add a constraint without an index. This will depend on what database you're using. Below is sample migration code for Postgres. (tracking_number, carrier) is a list of the columns you want to use for the constraint.
class AddUniqeConstraintToShipments < ActiveRecord::Migration
def up
execute <<-SQL
alter table shipments
add constraint shipment_tracking_number unique (tracking_number, carrier);
SQL
end
def down
execute <<-SQL
alter table shipments
drop constraint if exists shipment_tracking_number;
SQL
end
end
There are different constraints you can add. Read the docs
For completeness sake, and to avoid confusion here are 3 ways of doing the same thing:
Adding a named unique constraint to a combination of columns in Rails 5.2+
Let's say we have Locations table that belongs to an advertiser and has column reference_code and you only want 1 reference code per advertiser. so you want to add a unique constraint to a combination of columns and name it.
Do:
rails g migration AddUniquenessConstraintToLocations
And make your migration look either something like this one liner:
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
add_index :locations, [:reference_code, :advertiser_id], unique: true, name: 'uniq_reference_code_per_advertiser'
end
end
OR this block version.
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
change_table :locations do |t|
t.index ['reference_code', 'advertiser_id'], name: 'uniq_reference_code_per_advertiser', unique: true
end
end
end
OR this raw SQL version
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
execute <<-SQL
ALTER TABLE locations
ADD CONSTRAINT uniq_reference_code_per_advertiser UNIQUE (reference_code, advertiser_id);
SQL
end
end
Any of these will have the same result, check your schema.rb
Hi You may add unique index in your migration to the columns for example
add_index(:accounts, [:branch_id, :party_id], :unique => true)
or separate unique indexes for each column
In the typical example of a join table between users and posts:
create_table :users
create_table :posts
create_table :ownerships do |t|
t.belongs_to :user, foreign_key: true, null: false
t.belongs_to :post, foreign_key: true, null: false
end
add_index :ownerships, [:user_id, :post_id], unique: true
Trying to create two similar records will throw a database error (Postgres in my case):
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_ownerships_on_user_id_and_post_id"
DETAIL: Key (user_id, post_id)=(1, 1) already exists.
: INSERT INTO "ownerships" ("user_id", "post_id") VALUES ($1, $2) RETURNING "id"
e.g. doing that:
Ownership.create!(user_id: user_id, post_id: post_id)
Ownership.create!(user_id: user_id, post_id: post_id)
Fully runnable example: https://gist.github.com/Dorian/9d641ca78dad8eb64736173614d97ced
db/schema.rb generated: https://gist.github.com/Dorian/a8449287fa62b88463f48da986c1744a
If you are creating a new table just add unique: true
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.string :title, unique: true
t.text :body
t.references :user, foreign_key: true
t.timestamps
end
add_index :posts, :user_id, unique: true
end
end