Rails relationship - ruby-on-rails

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)

Related

setting up a belongs_to relation when the foreign key is stored in metadata

In a Rails 4 application, I have an STI model that stores metadata in a jsonb column.
Base Class:
class Post < ActiveRecord::Base
...
end
Subclass:
class JobPost < Post
# has a jsonb column for metadata
end
One of the data attributes in the metadata column of a JobPost is a foreign_key reference to another table (company_id). I'd like to add a belongs_to :company reference in the JobPost model. It seems like this should be possible by doing something like
class JobPost < Post
belongs_to :company do
Company.find_by_id self.metadata['company_id']
end
end
but that doesn't appear to work. Help?
Note: I am not necessarily intent on using belongs_to rather than writing needed methods like def company by hand, but I do need a way to eager load companies when listing job posts. If there's a way to do that eager loading without a belongs_to relation I'm all ears.
Update1
I have also tried the following, which doesn't appear to work either:
class JobPost < Post
belongs_to :company, foreign_key: "(posts.metadata->>'company_id')::integer".to_sym
end
Update2
To be more clear about my intentions and need:
1) A JobPost belongs_to a Company, but a Post (and other subclasses of Post) does not. I'd prefer not to jankily add the company_id column to the posts table when it won't be used by the other subclasses.
2) A JobPost could justify having it's own table (perhaps the relationship with a Company is enough to justify it). There are reasons why this wouldn't be ideal, but if that's the only answer I'm open to it. I'd, however, like a more definitive "what you're trying to do can't be done" response before going down this road, though.
The primary question is whether you can customize belongs_to so that it uses the metadata column rather than expecting the foreign key to be a column in the table.
The secondary question is whether you can eager load companies alongside job posts without having that belongs_to relation set up.
EDIT
UPD 2
You need to add "company_id" column to the base class of your STI table. If JobPost inherits from Post, and it should have "company_id" then add the "company_id" column to Post (base table).
Remember STI stands for "Single Table Inheritance" so there is only one table on database schema level. Imagine a column of a Post table, where few data records are the foreign key entries for Companies with company_id and what about the other records of this column with non JobPost subclass types, are they null/empty? Hence the relationship is defined with parent STI table and subclass inherits these relations. Additional type column in STI defines the subclass type.
Check here
You may need to dig further on Polymorphic classes instead of STI if both JobPost and Post have relationship with Company, else create two separate model, as they tend do have some unique relationships and column fields.
UPD
Based on updated ask
app/model/company.rb
class Company < ActiveRecord::Base
has_many :posts
delegate :jobposts, to: :posts
end
app/model/post.rb
class Post < ActiveRecord::Base
belongs_to :company
self.inheritance_column = :ptype
scope :job_posts, -> { where(ptype: 'JobPost') }
def self.ptype
%w(JobPost)
end
end
app/models/jobpost.rb
class JobPost < Post; end
Create a company
company = Company.create!(company_params)
Create some posts and add them to the company
company.posts << JobPost.new(jobpost_params)
To fetch jobpost by company relationship
company.job_posts
In case you are storing company_id in jsonb in any which column, just format your jobpost_params hash input accordingly and it should do the deed for you
OLD ASK
To find by primary key
Company.find(id)
In your case, id is self.metadata['company_id']
To find by other keys
Company.find_by(key: value)
Company.find_by_id is no more recommended
Please remove do and end after belongs_to in your model, instead in your controller you can write:
Jobpost.all.each do |x|
# your do
end
regarding foreign key, as rails is convention over configuration, it by default includes company_id reference to Jobpost which you can change in your Company.rb model

how to do a has_many relation on two tables?

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.

How do I add items to join tables in Rails? Do they get their own models?

I have two models: Reader and Magazine. I obviously want to have a join table, readers_magazines, to represent which magazines each reader is subscribed to.
So I create my Reader model (with fields like name, address and age) and my Magazine model (with fields like Title and Active?). In each model I write has_and_belongs_to_many of the other.
Then I write a migration, CreateReadersMagazinesJoin, and write:
create_join_table :readers, :magazines do |t|
t.index 'reader_id'
t.index 'magazine_id'
end
And migrate the database. All good.
My question is... what now? Do I create a model for the join table? That seems wrong, and yet I do need some model validations (I don't want to represent the same User-Subscription combo twice). So do I write a model for it and manually specify the database table to use?
What is the correct procedure in this situation?
From the rails guides:
A has_and_belongs_to_many association creates a direct many-to-many
connection with another model, with no intervening model.
That means that using a has_and_belongs_to_many association might be easier for you to setup (because there is no middle model) but you don't have any kind of control over the join table, validations, etc. HABTM are normally considered harmful and you would want to use a has_many :through relationship instead, that is the same but having a model to represent the join table, so you have control over everything.
More information here.
Do I create a model for the join table? That seems wrong, and yet I do need some model validations.
If you don't want to add an additional fields to the join table, you don't really need to create the model for it.
And in most cases the validation that are required by yours, can be applied to one or both the tables.
I don't want to represent the same User-Subscription combo twice.
What do you mean twice, I havent seen the cases for that, however since the User-Subscription is something external the for the join table, you can explain more crear.
Where you have the choice between a habtm relationship or separate model, I would consider whether you need a rich amount of data in the join. In the present case, I think you do need extra data in the join. For example, a subscription would have a start date and an end date. It may also have a billing date and billing amount. You would not normally populate this information in a join table but rather in a stand alone model.
I would call the join table subscriptions and thus create a model Subscription. Then you can do it like this:
class Reader < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, through: :subscriptions
end
class Subscription < ActiveRecord::Base
beongs_to :reader
belongs_to :magazine
end
class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, through: :subscriptions
end

Rails association primary_key option

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.

Moving a many to many to a one to one in rails

I have a relationship between two models that is currently a many to many. I'd like to move it to a one to one relationship, because that's what all of the data has turned out to be.
Is there a seamless way to do this? Via a migration and model change combo? My models would be simple to just change as shown below:
Now
class App < ActiveRecord::Base
has_and_belongs_to_many :owners
end
class Owner < ActiveRecord::Base
has_and_belongs_to_many :apps
end
Changing to
class App < ActiveRecord::Base
belongs_to :owner
end
class Owner < ActiveRecord::Base
has_one :app
end
But how do I update the database to reflect these changes without losing any of the data I currently have?
Add an owner_id field to your apps table and then iterate through your apps_owners table and set the owner_id field in apps to the owner_id field in apps_owners for the apps record whose id is equal to app_id.
Assuming you're correct and there aren't multiple apps_owners entries for the same app_id following SQL should do it (not tested), although I'm not familiar with how to incorporate raw SQL into migrations:
update apps set owner_id=(select owner_id from apps_owners where apps.id = apps_owners.app_id)
You won't be able to do this simply. The main problem is that has_and_belongs_to_many relationships use a tertiary database table (apps_owners in your case) to hold the relationship information.
After you create the appropriate migration to add owner_id to your App model, you'll need to create a rake task that reads the app_owner table and re-creates the relationships.

Resources