The Rails Guide on Active Record Migrations section 3.2 Creating a Join Table says that indices are not added by default to join tables:
create_join_table also accepts a block, which you can use to add
indices (which are not created by default) or additional columns:
When I run the generator, I can indeed see that at least some indices are not added by default:
class CreateJoinTableFooBar < ActiveRecord::Migration
def change
create_join_table :foos, :bars do |t|
# t.index [:foo_id, :bar_id]
# t.index [:bar_id, :foo_id]
end
end
end
What is the rationale for Rails not adding these indices by default?
Also, just to clarify, if I don't uncomment these lines, will any indices for :foo_id or :bar_id by itself be generated?
not added by default to HABTM join tables
The key is here: HABTM
has_and_belongs_to_many is meant to be a way to simply connect two or more sets of data together. There is no need for primary indexes - simply two sets of foreign_keys
The reason you may wish to add indices etc is because of has_many :through (which is used standalone predominantly to give you extra attributes in the join).
Although has_many :through is meant to give you the ability to use a central model to join two others...
... it is often used just as a join model (IE in the example above, the join model could be called physician_patients).
If you were using has_many :through in this capacity, you'd want to have indices in your join table.
Related
I previously created a joined table with the migration:
class CreateJoinTableCategoryListing < ActiveRecord::Migration[5.2]
def change
create_join_table :categories, :listings do |t|
# t.index [:category_id, :listing_id]
# t.index [:listing_id, :category_id]
end
end
end
I was looking back on this as i am going to be creating a new join tables. But while looking at it i noticed i migrated with the t.index's still commented out and the joined tables still operate correctly.
I read into this and i haven't found any posts about either someone doing the same or not needing them.
How is it operating with those index's never migrated and how needed are they?
I am creating a new migration:
class CreateJoinTable < ActiveRecord::Migration[5.2]
def change
create_join_table :users, :affiliates do |t|
# t.index [:user_id, :affiliate_id]
# t.index [:affiliate_id, :user_id]
end
end
end
Which index should i be choosing here?
How it should work is that an affiliate is able to manually submit a "commission" to the table (which does need to be added to the migration), but if the commission is updated, it should take the place of the column and not create a new row.
A user will have really nothing to do with this and will be mostly updated by the affiliate to update the commission rates they have on the user.
Update:
Is it even possible to add another field to the join table?
I wanted to add a :commission to the table but i can't find any docs to do anything for that. Should i just be defining the commission rate within the users table and do away with the join table?
UPDATE 2:
Ended up scratching this idea and keeping my current method of doing it with the users and affiliates association only. I did away with the UsersAffiliates idea as it's not needed for this case.
How is it operating with those index's never migrated and how needed
are they?
All types of assocations in Rails will work without indices. The only thing that is required is that the correct tables and columns exist.
Indices are however critical for performance as the size of your database grows. They also provide constraints such as uniqueness that ensure that duplicate data cannot be inserted due to race conditions.
Which index should i be choosing here?
The whole reason that Rails generates two different indices is that you should choose the index that cooresponds to how you will most often be searching the table. If you are most often using User.joins(:affilitates) you would choose t.index [:user_id, :affiliate_id] for example.
How it should work is that an affiliate is able to manually submit a
"commission" to the table (which does need to be added to the
migration).
The create_join_table macro creates a join table named for has_and_belongs_to_many assocations.
The main problem with has_and_belongs_to_many assocations is that they are headless. There is no model and therefore no way to query the table directly or add additional metadata columns.
What you instead want is a has_many :through association.
class User < ApplicationRecord
has_many :user_affiliates
has_many :affiliates, through: :user_affiliates
end
class Affiliate < ApplicationRecord
has_many :user_affiliates
has_many :affiliates, through: :user_affiliates
end
# call this whatever you want
class UserAffiliate < ApplicationRecord
belongs_to :user
belongs_to :affilitate
end
While has_and_belongs_to_many uses the table naming scheme users_affilities (plural_plural) you want to use user_affilities for a has_many through: association.
You can fix this by:
Just generating the table/model through the normal generator rail g model user_affiliate.
If the table exists write a migration to rename the table.
but if the commission is updated, it should take the place
of the column and not create a new row.
You can solve this by:
Add a unique compound index on the two columns t.index [:user_id, :affiliate_id], unique: true.
Add a uniqueness validation in the join model. validates_uniqueness_of :user_id, scope: :affiliate_id.
Use .find_or_initialize_by in your controller to update an existing row if it exists instead of creating a new row if one already exists.
Currently, I have two classes Theme and Offer mapped by a join table themes_offers containing theme_id and offer_id.
I need to implement a view with custom order of offers for each theme.
So the current idea I have is to add a column on the table, and create new activerecord class mapped to the table:
class AddOrderToThemesOffers < ActiveRecord::Migration[5.0]
def up
add_column :themes_offers, :order, :integer
# mark order for each existing orders
add_index :themes_offers, [:theme_id, :order], unique: true, name: 'index_offer_order_on_theme'
end
def down
remove_index :themes_offers, 'index_offer_order_on_theme'
remove_column :themes_offers, :order, :integer
end
end
Would there be a better approach? The problem I have with this solution is that it will be difficult to implement activeadmin interface to handle the orders.
A Rails has_and_belongs_to_many relationship is designed to create the relationship by means of a table that has two columns only: an id for each table and nothing else, not even an id of its own. See the docs here.
I think what you want is a has_many :through relationship, which will allow you to have a themes_offers table with additional attributes on it. Docs are here.
I'm trying to do a has_many relation with a table on creation and also to add it in another table already created.
I have my User table already with no relations.
I want to create a Pro_user with a relation has_many User.
The thing is that one User can have multiple Pro_user and a Pro_user can also also have multiple User.
So both tables need a has_many right ?
So what I thought of was that
rails g model pro_user name:string email:string password_digest:string user:references
But this is not the right thing, it's doing a belongs_to from Pro_user to User
And also how should I do to do add the has_many on my existing table User ? Do I have to do a migration to recreate the table and adding the relation ?
Thanks for your help !
The recommended approach for a many to many association is the "has_many_through" approach. This allows you to add additional columns later on to the join table if you need more data. You'll need a join table that will at the least have two reference columns to your Users and ProUsers tables along with the standard id column.
(Refer to: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association)
So your User and ProUser tables will not have any reference columns in them. Instead you'll make a third table called BoatsAndPros (call it whatever you like) and do:
create_table :boats_and_pros do |t|
t.belongs_to :user, index: true
t.belongs_to :pro_user, index: true
t.timestamps
end
Then in the corresponding boats_and_pros.rb Model file you'll add:
belongs_to :user
belongs_to :pro_user
In your user.rb file you'll add:
has_many :boats_and_pros
has_many :pro_users, through: :boats_and_pros
In your pro_user.rb model file you'll add
has_many :boats_and_pros
has_many :users, through: :boats_and_pros
Two key takeaways are:
The "oldschool" has_and_belongs_to_many approach is still fine however doesn't allow room to grow like the has_many_through approach here and you'll need to specifically name the table pro_users_users because Rails expects the two tables to be listed in lexical order.
One-to-many relationships like what you ended up with on your original attempt keep the reference in one of the tables while many-to-many relationships require a third join table as shown above.
I'm trying to figure out something regarding rails relationships. I already posted a question regarding a specific items not long ago but I do not really understand what's done in the underlying DB.
I have a Project model and a Client model.
A Project belongs_to :client => I need to manually add client_id in projects table (with a migration).
A Client has_many :projects => I do not need to do anything in the DB (no migration).
The project.client and client.projects methods are both available.
I have a Group model and a User model.
A Group has_and_belongs_to_many :user
A User has_and_belongs_to_many :group
I then need to create a migration to create a joint table with a user_id and a group_id pointers.
I do not really see where the border between rails and the relational database is.
Why do I need to add foreign key sometimes but not always ? How is the has_many relationship handled as I did not do anything in the underlying DB for this particuliar guy ?
I am kind of lost sometimes :)
Thanks and Regards,
Luc
For a has_many <-> belongs_to assoication, you're defining that one project is owned (belongs_to) by one client. Therefore, that client has many (has_many) projects. For a project to determine what client it belongs to it needs to have an client_id column so that it can look it up. This client_id column is used by Rails when you call the client method, much like this:
Client.find(project.client_id)
That's how you can find a project's client. The client_id column is often referred to as a foreign key, because its a unique identifier ("key") in a table not of its origin ("foreign"). Boom.
When you call the other way around, finding all the projects a client has, i.e. client.projects, Rails does the equivalent of this:
Project.find_all_by_client_id(client.id)
This then returns all Project records which are associated with a particular client, based off the client_id field in the projects table.
With a has_and_belongs_to_many association, such as your users & groups example, you're declaring that a user has_and_belongs_to_many :groups.
Now if it were simply a has_many :groups, the foreign key would go in the groups table, or if it were a belongs_to it would go in the users table. Good thing to remember: the foreign key always goes in the table of the model that has the belongs_to.
You're also declaring that a group has_and_belongs_to_many :users, and so we come across the same problem. We can't declare the key in the users table because it doesn't belong there (because a user has many groups, you would need to store all the group ids the user belongs to) or the groups table for the same reasons.
This is why for a has_and_belongs_to_many we need to create what's known as a join table. This table has two and only two fields (both of them foreign keys), one for one side of the association and another for the other. To create this table, we would put this in a migration's self.up method:
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
A couple of things to note here:
The table name is the two names of the two associations in alphabetical order. G comes before U and so the table name is groups_users.
There's the :id option here which, when given the value of false generates a table with no primary key. A join table doesn't need a primary key because its purpose is to just join other tables together.
We store the group_id and user_id as integer fields, just like we would on a belongs_to association.
This table will then keep track of what groups have what users and vice versa.
There's no need to define additional columns on either the users or groups table because the join table has got that under control.
class Customer < ActiveRecord::Base
has_many :orders, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
#order = #customer.orders.create(order_date: Time.now)
I'm struggling now to get HATBM working correctly. I have a beaten scanario: articles and tags. I presume, HABTM should be used here, since it is a many-to-many relationship.
I don't know however if I should manually create a join table (articles_tags in this case).
My code currently as follows:
class Article < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :articles
end
When I run the migrations, no 3rd table is created.
Also, I would like to add that my third table doesn't bear any domain logic, just blind assignment.
I'm using Rails 2.2.2
You should do this in a migration of one of the tables, or in a separate migration if those migrations have been ran:
create_table :articles_tags, :id => false do |t|
t.references :article, :tag
end
add_index :articles_tags, [:article_id, :tag_id]
This will create the table for you and the :id => false tells Rails not to add an id field to this table. There's an index also, which will speed up lookups for this join table.
You could also generate a model (ArticlesTag) for this and do:
# article.rb
has_many :articles_tags
has_many :tags, :through => :articles_tags
# tag.rb
has_many :articles_tags
has_many :articles, :through => :articles_tags
# article_tag.rb
belongs_to :tag
belongs_to :article
And then create the table in the migration generated from the script/generate model articles_tag call.
Note that this is covered in the API.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many
You probably also want to add an index to the migration:
add_index "articles_tags", "article_id"
add_index "articles_tags", "tag_id"
However, if you want tagging functionality I'd recommend the acts_as_taggable_on rails plugin:
http://www.intridea.com/tag/acts_as_taggable_on
http://github.com/mbleigh/acts-as-taggable-on/
I've used it on a project and it was very easy to implement.
One of the issues with a join table for tagging is that it can easily get ugly creating a join table for each content type you wish to make taggable (ie. comments_tags, posts_tags, images_tags, etc). This plugin uses a taggings table which includes a discriminator to determine the content type without the need of a specific join table for each type.
In combination with this Qeuestion(1st answear) How to set up a typical users HABTM roles relationship and 1st answear from here, it has to be understood even by a monkey. I am new in RoR and it's got working like a charm