Rails association primary_key option - ruby-on-rails

I understand that foreign keys are for specifying when you have a different column name (different than parent child's class name) on the child class. I know what primary keys and foreign keys are and have read the rails documentation on associations several times, but I can't figure out what the primary key option is for.
1) But what is the primary_key option for? How does it change the sql when an association is called?
2) In what instances would you need to specify the primary_key on association?
3) In what instances would you need to specify both the primary_key and foreign_key?
below is an example of specifying the foreign_key option on associations:
class User
has_many :texts, foreign_key: :owner_id
end
class Text
belongs_to :user, foreign_key: :owner_id
end
User Table
id| name |
Text Table
id| owner_id |name

Ok so I thought more about the SQL and figured it out. You use foreign_key option when your child has a different foreign_key name then your parent's classname_id BUT on your parent table, you are still using ID as your identifier.
user table
id|name|age
text table
id|random_id|conversations
select * from user where user.id = text.random_id
select * from text where text.random_id (foreign_key) = account.id (primary key)
On the other hand, you use primary_key with foreign_key when you don't want to use id at all to link the relationship.
user table
id|userable_id|name|age
text table
id|userable_ss_id|conversations
HERE: if you wanted to link the userable_ss_id to userable_id, you would include both primary_key and foreign_key options on both relationships.
class User
has_many :texts, primary_key: :userable_ss_id, foreign_key: :userable_id
end
class Text
belongs_to :user, primary_key: :userable_ss_id, foreign_key: :userable_id
end
Basic rule of thumb:
select * from text where text.(foreign_key) = account.(primary key)

primary key concerns the main table and foreign key the associated one
U 've used the foreign key correctly. If for isntance User had another primary key than :id u'd have to specify that either.
#User
has_many :texts, primary_key: :uuid, foreign_key: owner_id
So you need this options, if you want to have another naming of the keys than rails conventions assume for the main and associated table respectivly

Rails uses convention over configuration.
By convention, all database tables in rails have a primary-key of the id column.
If, for some odd reason (eg you've got a legacy database), your table uses a different primary key to id... you use the primary_key call to tell rails what it is.
By convention, all associations use a foreign-key of <model>_id for the foreign key.
If, for some reason, your association uses a different foreign-key to find the associated model - you'd use foreign_key to tell rails what it is.
Unlike primary_key, using foreign_key can be much more common. Especially when you have more than one association using the same table but with different association names.

Related

using has_many: primary_key when it isn't the primary_key in the database

I'm working on a codebase that uses rails and the previous developers have created a few models in postgres that are references to each other. However they use activerecord's primary_key field even though the database doesn't show that field as the primary_key. Is there any benefit or disadvantage to this? Keep in mind there is an index on the 'fake' primary_key field .
# models
class A < ActiveRecord:Base
has_many :bs, primary_key: :special_id
end
class B < ActiveRecord::Base
belongs_to :a, primary_key: :special_id, foreign_key: :special_id
end
Actual primary_key is id on B but schema includes id and special_id. Index exists on special_id
The benefit, in theory, is that it's more readable. In your case A.find(id).bs would join the Bs that have a corresponding b_id in your A table, which may not be as intuitive as using another name, in this case special_id.
From a performance standpoint it should make no noticeable difference, provided there are indexes on both the primary and foreign key columns. The has_many method will ultimately result in a query, so the only computational cost is to swap out the column name in the query string that ActiveRecord generates.
You could try benchmarking the queries with the various key declarations, but I'd be amazed if it added even a millisecond to the query time.

do join tables need to use conventional naming in rails?

say I have company and employee tables, does my join table have to be called companies_employees? I don't see anything about this on the rails documentation.
Name of join table can be changed. Here is document of has_and_belongs_to_many.
If the default name of the join table, based on lexical ordering, is
not what you want, you can use the :join_table option to override the
default.
So, you can change it like this:
# Company model
has_and_belongs_to_many :employees, join_table: "comp_emps"
# Employee model
has_and_belongs_to_many :companies, join_table: "comp_emps"
No, in fact none of the tables have to be named a particular way -- that's just convention. You can override the table name for any model, but usually you would do because you have a legacy schema.
And similarly, if you have a model that belongs to both Company and Employee it doesn't have to be named CompanyEmployee (conventional table name would be company_employees) or EmployeeCompany (conventional table name employee_companies, but it often makes sense to.

Foreign key that doesn't point to a primary key in rails and postgres?

I'm using Rails with a legacy postgres database, meaning no migrations. The schema is defined separately.
I have two tables: notification and notification_type. The notification_type table has an ID (primary key) and a code column. The notification table has a notitification_type column with a foreign key on the notificiation_type table's code column.
class Notification < ActiveRecord::Base
...
belongs_to :notification_type, foreign_key: 'notification_type'
...
NotificationType:
class NotificationType < ActiveRecord::Base
has_many :notifications
end
When I call create on the notification model, it gives me a foreign key error because it's trying to create with the ID column of notification_type, not the code column.
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "notification" violates foreign key constraint "notification_notification_type_fkey"
DETAIL: Key (notification_type)=(18) is not present in table "notification_type".
Where 18 is the ID, but I need it to use the code, which would be something like 1 or 2. I saw the association_foreign_key value, but it seems that it only works with the has_and_belongs_to_many association.
psql, just for closure:
Foreign-key constraints:
"notification_notification_type_fkey" FOREIGN KEY (notification_type) REFERENCES notification_type(code)
Basically, how do I get rails to honor the foreign key relationships in my database instead of always using the primary key?
Just do:
belongs_to :notification_type, foreign_key: 'notification_type', primary_key: 'code'
The primary_key defaults to id but you can use anything you want.
You'll want to specify the same options for your has_many :notifications association - the has_many is completely unaware of it's associated belongs_to, so it needs to know if you're using non-standard keys as well.

Activerecord relation with custom keys

_categoriesI'm trying to re-create some legacy database relation using Activerecord. I'm an Activerecord newbie, by the way I solved every problem until now except for some association where the key is not the ID.
I have two tables:
Product
id
model
name
type
DataCategory
id
name
tree
The association here is driven by "type" and "tree": how can I create this association with ActiveRecord?
I try with:
has_many :data_categories, foreign_key: 'type', primary_key: 'tree'
but it doesn't work
I think you need inverse foreign_key and primary_key like this:
has_many :data_categories, foreign_key: 'tree', primary_key: 'type'
Hope it helps
What is stored in 'type' and 'tree'? The foreign key is the database field on DataCategory that holds the unique id of the Product. The primary key is the database field on Product that holds that unique id. By default, they would be product_id and id respectively. In your case, assuming 'tree' holds the product id, it would be
has_many :data_categories, foreign_key: 'tree'
It's not necessary to specify the primary key because you appear to be using the default of 'id'

Rails relationship

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)

Resources