Rails belongs_to association discussion

Let’s say I have a model A which belongs to model B, and model B belongs to models C and D. The image below shows a visual image of these belongs_to associations.

image

Now, A.reflect_on_all_associations(:belongs_to) just returns model B because it only goes one level deep. I don’t know the internal details of belongs_to, but when I read it in English, it seems like it should return models B, C, and D instead of just model B.

My question is basically, if a model A belongs_to model B and model B belongs_to model C, then does it also make sense for model A to belong_to model C or is there some internal detail for why model A should not belong_to model C?

The belongs_to association essentially represents the database concept of a foreign key, and foreign keys are not transitive.

Let’s say that for your example these models relate to this stuff:

A - Item
B - Order
C - Customer
D - Salesperson

So customer Fred places an order for a Widget, let’s say. Just because that widget belongs to a specific order doesn’t mean that any kind of inheritance-style things would naturally happen to take things further. But you can write a query that would further JOIN in other additional tables. Or you could use a has_many through association from Customer back through Order to Item, and then see how things would be associated. Here’s how those models might look:

class Item < ApplicationRecord
  belongs_to :order
end
 
class Order < ApplicationRecord
  belongs_to :customer
  belongs_to :salesperson
  has_many :items
end
 
class Customer < ApplicationRecord
  has_many :orders
  has_many :items, through: :orders
end
 
class Salesperson < ApplicationRecord
  has_many :orders
  has_many :items, through: :orders
end

So then you could find a customer by name, and from there see all the items they’ve ever ordered:

Customer.find_by(name: 'Fred').items

It is not possible to create a “belongs_to through” kind of association, but you can write a method that would function in that kind of way. You could add this to Item in order to find the associated customer:

  def customer
    order.customer
  end

Even better might be to get all the data back using one query by eager loading related models:

widget = Item.where(name: 'Widget').eager_load(order: :customer)

And although the object which comes back looks like it’s only an Item, in memory it also has an attached Order and Customer which can be referenced without using additional database queries.

1 Like