Say I have three models...
Product
belongs_to :ProductCategory
belongs_to :Manufacturer
ProductCategory
has_many :products
Manufacturer
has_many :products
I'd like to ask an instance of ProductCategory for the set of Manufacturers for Products in that ProductCategory with a call like product_category.manufacturers.
I've currently implemented it in the Products model like this:
def manufacturers
Manufacturer.find(self.products.pluck(:manufacturer_id).uniq.to_a
end
Is there a better "rails way"?
Thanks!
Yes, this is an extremely well-solved problem and a fundamentally basic part of using Associations in Rails. You want has_many :through:
class ProductCategory
has_many :products
has_many :manufacturers, :through => :products
end
Related
class Category
has_many :services
end
class Service
belongs_to :category
belongs_to :order
end
class Order
has_many :services
end
How to organize a connection, where I can refer to orders from an object of the category,
It is natural that orders are specifically in this category for services that are
they have.
For example:
category = Category.first
category.orders
Use has_many :through.
class Category
has_many :services
has_many :orders, through: :services
end
I recommend you take a look at the Rails association basics guide.
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.
I have this relationship between categories, products & brands:
class Brand < ActiveRecord::Base
has_many :products
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :brand
end
How can I select all categories by specified brand with this relations?
I try this but get an error
b = Brand.find(1)
Category.joins(:products).where(:products => b.products)
You did the right thing with the join, just add a more complex where definition:
Category.joins(:products).where(:products => {:brand_id => 1})
Controversially HABTM's are rarely, if ever, a good design and IMO just about the only thing Rails got wrong.
Introduce an xref table to join products and categories and use has_many :through on both sides of the relationship so you end up with
class Brand < ActiveRecord::Base
has_many :products
has_many categories :through => products # This is now allowed in Rails 3.x and above
end
class Category < ActiveRecord::Base
belongs_to :product_category
has_many :products :through => product_category
end
class Product < ActiveRecord::Base
belongs_to :brand
belongs_to :product_category
has_many :categories :through => product_category
end
class ProductCategory < ActiveRecord::Base
has_many :products
has_many :categories
end
This gives you the best flexibility with the least amount of code re-factoring for you plus a much more intuitive path to get whatever data you need on either side of the relationship and will enable you to achieve the following
b = Brand.find(1)
b.categories.all
Update
The above is totally untested code and I have just corrected a glaringly stupid mistake I made. If you have any issues implementing this then come back
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
What I'd like to do is join one model to another using two intermediary models in between..Here's the abstraction:
Country has_many Companies
Company has_many Buildings, Company belongs_to Country
Building has_many Rooms, Building belongs_to Company
Room belongs_to Building
I want to be able to do Country.first.rooms, so I thought the Country model would be as simple as:
class Country < ActiveRecord::Base
has_many :companies
has_many :buildings, :through=>:companies
has_many :rooms, :through=>:buildings
end
However, this tries to generate SQL like:
SELECT * FROM rooms INNER JOIN buildings ON rooms.building_id = building.id WHERE ((building.country_id = 1))
Obviously, building.country_id does not exist. How do I get around this?
The built in association methods won't help you here. You need to build the query explicitly using joins:
class Country - ActiveRecord::Base
has_many :companies
has_many :buildings, :through=>:companies
def rooms
Room.all :joins => { :building => { :company => :country } }, :conditions => { "countries.id" => id }
end
end
This will require the belongs_to associations to be set up on the Building and Company models
Try using nested has many through
This is possible after Rails 3.1. The below code works for me. It's important to specify the has_many at each level.
class Country
has_many :companies
has_many :buildings, through: :companies
has_many :rooms, through: buildings
end
class Company
has_many :buildings
end
class Building
has_many :rooms
end
class Room
end