has_many, has_one association with composite foreign keys - ruby-on-rails

I am trying to wrap my head around how I am going to be able to have a has_many (or has_one in my case) association with a compound foreign_key.
For my example Assume this:
Imagine I am managing relationships between sellers and buyers, there is an initial contract between them and after that any amount of invoices. So the models look like this:
class Invoice
belongs_to :seller
belongs_to :buyer
end
class Seller
has_many :contracts
has_many :invoices
end
class Buyer
has_many :contracts
has_many :invoices
end
class Contract
belongs_to :seller
belongs_to :buyer
end
For every invoice there is one initial contract (defined by the seller and the buyer). I'm aware that there is all kinds of solutions to build a query that achieves that. Ideally though I want to take care of preloading/joining the contract for a list of invoices to avoid N+1 problems:
class Invoice
belongs_to :seller
belongs_to :buyer
has_one :contract # options go here
end
EDIT: The schema is what it is. Solutions that require a schema change unfortunately is not an option.

Just make the invoice belong to the contract and get the buyer and seller from that.
class Invoice
belongs_to :contract
end
Then get the invoices through contacts...
class Buyer
has_many :contracts
has_many :invoices, through: :contracts
end
class Seller
has_many :contracts
has_many :invoices, through: :contracts
end

Related

What references, foreign_keys do I need to add for these associations?

I have these associations in my Rails 4.2 app. I don't understand how to setup the proper references/foreign_keys between instructors and courses.
So far both instructors and courses tables have a local_id (local reference).
Local is a training center they both belong to.
class Local < ActiveRecord::Base
has_many :instructors
has_many :courses
end
class Instructor < ActiveRecord::Base
belongs_to :local
has_many :courses, through: :locals
end
class Course < ActiveRecord::Base
belongs_to :local
has_many :instructors, through: :locals
end
Do I add a foreign_key to the courses table? Like this:
add_foreign_key :courses, :instructors
I read something about when having many to many associations we need a "join table" cause we need to store many ids. I guess Local is just that in this case.
Or do I need another table(Model) that belongs_to :instructor, :course?
Here is how I would set it up for maximum flexibility.
Instructors and Courses
Lets set up a many to many relationship with a join model which we call Employment.
We can generate the model with:
rails g model employment instructor:belongs_to course:belongs_to
Which will give us this:
class Employment < ActiveRecord::Base
belongs_to :instructor
belongs_to :course
end
employments will have the instructor_id and course_id foreign keys. This lets us assign any number of instructors to a course and vice versa.
So let's put the join model to use:
class Instructor < ActiveRecord::Base
has_many :employments
has_many :courses, through: :employments
end
class Course < ActiveRecord::Base
has_many :employments
has_many :instructors, through: :employments
end
Venues and Courses
I would recommend you rename your Local model Venue as it's the common english term for the place where a course or event is held. Locale is awkward since it collides with a separate concept in web applications and may cause name clashes.
And we should probably set it up as many to many to account for the complexities of real life.
So again:
rails g model booking venue:belongs_to course:belongs_to
class Booking < ActiveRecord::Base
belongs_to :venue
belongs_to :course
end
bookings will have the venue_id and course_id foreign keys.
class Venue < ActiveRecord::Base # was Local
has_many :bookings
has_many :courses, through: :bookings
end
class Course < ActiveRecord::Base
# ...
has_many :bookings
has_many :venues, through: :bookings
end
More reading:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/

Rails associations between clients and sellers - has_many :through or has_and_belongs_to_many

I'm beginning a project with rails where there are products, clients and sellers. Each seller has_many products. Each Client has_many products. (And in my case, each client only buys one product at a time).
I want to know who are my clients' seller and my Seller's clients, knowing that, they'll be linked by, the purchase, of one product.
Should I use a has_and_belongs_to_many association between clients and sellers ? Or a double has_many through :products, like :
Seller :
has_many :clients through :products
Belongs_to :products
Client :
has_many :sellers through :products
Belongs_to :products
In order to avoid two belongs_to in the product class, could this work ?
class Client < ActiveRecord::Base
has_many :products, as: :productable
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :products, as: :productable
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :productable, polymorphic: true
end
Thanks in advance for your answer.
I would go with has_many :through here.
class Client < ActiveRecord::Base
has_many :products
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :prodcuts
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :client
belongs_to :seller
end
The simplest rule of thumb is that you should set up a has_many
:through relationship if you need to work with the relationship model
as an independent entity. If you don't need to do anything with the
relationship model, it may be simpler to set up a
has_and_belongs_to_many relationship (though you'll need to remember
to create the joining table in the database).
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
And also see these Guides for choosing between HABTM and a has_many :through
I want to approach your question from the other end: let us start from the product. I think this will clarify a lot of things.
So you have three models: Seller, Client and Product.
A Product has a seller and client. In your model that would like this:
class Product
belongs_to :seller
belongs_to :client
end
This means that in the products table we have a column seller_id and client_id.
Afaik a product needs to have both, always. So this also means you cannot use a polymorphic association here. At least not the way you proposed it. If you write
belongs_to :productable, polymorphic: true
you will add the fields productable_id and productable_typeto yourProduct` model. But that is only 1 link (so either a seller or a client, but never both). You could introduce a link table here, so a product could be linked to many "productables" but in your case i think it is besides the point. You know a product has one seller and one client.
Secondly, now this is established, your Product is exactly the link-table between clients and sellers. So you do not have to introduce a new link-table, just use the one already there.
class Seller
has_many :products
has_many :clients, through: :products
end
class Client
has_many :products
has_many :sellers, through: :products
end
So in conclusion:
use the has_many :through because you already have the link table as a model. Only use a habtm if you do not care about the join-table (link-table).
you can't use a polymorphic association here, as you need two links (without introducing a link-table, which seems overkill imho). I like the explicitness, clarity, readability of having an explicit seller_id and client_id, and it is also easier to manage.

Rails belongs_to_many

I'm a beginner in Rails and I have a problem with ActiveRecords associations.
I'm creating simple car rental service and I made the following associations:
class Client < ActiveRecord::Base
has_many :rentals
has_many :bookings
has_many :cars, :through => :rentals
has_many :cars, :through => :bookings
end
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_one :car
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_one :car
end
What I need is to have a car belonging to many bookings and rentals while every booking and rental can have only one car assigned.
class Car < ActiveRecord::Base
# belongs_to_many :bookings
# belongs_to_many :rentals
end
How should I do that?
If a car can have many bookings/rentals, but a booking/rental can only have one car, you're looking at a classic belongs_to/has_many situation. It looks like you're being tripped up by the distinction between belongs_to and has_one -- it's not a grammatical one, but a matter of where the foreign key column is located in your database.
belongs_to: "I am related to exactly one of these, and I have the foreign key."
has_one: "I am related to exactly one of these, and it has the foreign key."
has_many: "I am related to many of these, and they have the foreign key."
Note that has_one and has_many both imply there's a belongs_to on the other model, since that's the only option where "this" model has the foreign key. Note also that this means has_one should only be used when you have a one-to-one relationship, not a one-to-many.
Taking this into consideration, I would replace the has_one :car with belongs_to :car in both your Rental and Booking models, and place has_many :bookings and has_many :rentals in your Car model. Also ensure that your rentals and bookings tables have a car_id column; there should be no rental- or booking-related columns in your cars table.
Yes, there is a "belongs_to_many" in Rails, sort of. It's a little more work and you can't use generators with it. It's called a polymorphic association.
Even though you could make a car have many bookings & rentals, you could associate the car by making it belong to a polymorph such as rentable_vehicle. Your code would look like this
class Car < ActiveRecord::Base
belongs_to :rentable_vehicle, polymorphic: true
end
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_many :cars, as: :rentable_vehicle
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_many :cars, as: :rentable_vehicle
end
You can't do belongs_to_many. The closest you can really get is has_and_belongs_to_many, but I'm not sure that's what you want here - unless you can have multiple cars per rental/booking. Check out the guide for a full explanation.
I'd change it up like this:
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :car
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :car
end
class Car < ActiveRecord::Base
has_many :bookings
has_many :rentals
end
Also, I don't know how your rentals relate to bookings, but my immediate thought is that there should be some relationship between the two, because you probably can't have a rental without booking it, right?

One to many relationships in Rails

I'm having a hard time setting up the basic structure of my databases.
I have products (about 50). Each products is related to one or more place(s).
The basic schema would be this (no relation yet)
Products
id:integer
name:string
Places
id:integer
name:string
content:string
I first tought I could connect places and products by adding place_id to products and has_many belong_to in the controllers, but since a product can have more than one place, I don't know how.
If a Product has_many Places and a Place has_many Products your association needs to be many-to-many.
There are two ways to do this in Rails, the most recommended is a join model. You explicitly label the relationship between Products and Places. You could call it ProductLocation or Shop or similar, it would look like this:
product_locations:
product_id
place_id
class ProductLocation < ActiveRecord::Base
belongs_to :product
belongs_to :place
end
class Place < ActiveRecord::Base
has_many :product_locations
has_many :products, :through => :product_locations
end
class Product < ActiveRecord::Base
has_many :product_locations
has_many :places, :through => :product_locations
end
See http://guides.rubyonrails.org/association_basics.html#choosing-between-has_many-through-and-has_and_belongs_to_many
Just use through connection
ProductsPlaces (new table)
product_id
place_id
And in models
class Product < ActiveRecord::Base
has_many :places, :through => :products_places
end
class Place < ActiveRecord::Base
has_many :products, :through => :products_places
end
Also there is has_and_belongs_to_many which do in fact the same (with ProductsPlaces table)
class Product < ActiveRecord::Base
has_and_belongs_to_many :places
end
class Place < ActiveRecord::Base
has_and_belongs_to_many :products
end
But better use through, because has_and_belongs_to_many will be deprecated.
It sounds like you want to add product_id to places.
Product has_many Places
Place belongs_to Product

has_many :through usage, simple, beginner question

I am writing a simple accounting system for managing costs. The structure is like this:
Invoice - can have many products
Product - can have many costs, also can act_as_tree
LineItem -
Product
LineItem
LineItem
Product
LineItem
Product
LineItem
LineItem
I had this set up as a has_many and belongs_to for the three classes but think that the following is more appropriate (based upon reading Rails 3 Way - any shortcomings are my lack of understanding; just trying to give context)
Class Invoice < ActiveRecord::Base
has_many :products
has_many :line_items, :through => :products
end
Class Product < ActiveRecord::Base
belongs_to :invoice
belongs_to :line_item
end
class LineItem < ActiveRecord::Base
has_many :products
has_many :invoices, :through => :invoices
end
But I don't this is working correctly.
if I do the following:
>#i=Invoice.find(1)
>#i.products # this works
>#i.line_items # doesn't work, unidentified method line_items
This is the first time I'm using has_many :through. Is this set up correctly for my data model? Also, is it possible to use it in conjunction with acts_as_tree - I'd like to be able to say:
>#i.line_items
and get back all the line items for that specific invoice. Possible?
thx for help
First a question: What is your relation between Product and LineItem: Has 1 product many line items or is 1 and the same line item referenced in many products? The rest of this answer is based on the assumption that every product should have multiple line items.
I think your models should be defined like that:
# No change
Class Invoice < ActiveRecord::Base
has_many :products
has_many :line_items, :through => :products
end
# Changed the relation to :line_items
Class Product < ActiveRecord::Base
belongs_to :invoice
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :products
# The following then does not make sense
##has_many :invoices, :through => :invoices
end
why did you choose this structure? in such cases i usually do
Class Invoice < ActiveRecord::Base
has_many :line_items
has_many :products, :through => :line_items
end
Class Product < ActiveRecord::Base
has_many :line_items
# if you need
has_many :products, :through => :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :products
belongs_to :invoice
end
this fullfills following requirements:
Invoice and Product have a Many2Many relationship
The relationship between an Invoice and a Product is a LineItem, which provides further information like price, amount, applied taxes, etc.
You can create a LineItem by adding a Product:
invoice = Invoice.create
invoice.products << Product.find_by_name('awesome')
Your Invoice should only have line items! These can be a tree structure but you should not have products directly referenced from your invoice (what if a product price changes: this would affect existing invoices!)
So, to fix your structure:
invoice
line_item # references a product, but has its own fields (price!)
line_item
line_item
Each line_item should reference one product using belong_to. It's your join model between invoices and products:
class Invoice
has_many :line_items
end
class LineItem
belongs_to :invoice
belongs_to :product
acts_as_tree # (implies has_many :line_items with the parent invoice_id, etc.)
end
class Product
has_many :line_items
end
This is basically all you need to build an invoice. You can think of the tree structure as being independent from the Invoice:LineItem:Product relationship. It can work as a flat list, or enhanced to become a tree.
If your products normally contain other products and you need to know which children to add to the invoice when the parent product is added (as line items), you'll need to tree your products too:
class Product
has_many :line_items
acts_as_tree
end
This tree structure is independent of the tree structure in line items. Remember: products can change, and this shouldn't affect existing line items in your invoices.

Resources