steventnorris
steventnorris

Reputation: 5896

Rails has_many chain

So I have a table structure below:

Plane

Seating

PlaneSeating

PlaneSeatingNote

Note

This gives me a third-normal DB, but I need to set the model relations up.

I currently have:

class Plane < ActiveRecord::Base

  has_many :plane_seatings, dependent: :destroy
  has_many :seatings, through: :plane_seatings

end

class Seatings < ActiveRecord::Base

  has_many :plane_seatings, dependent: :destroy
  has_many :planes, through: :plane_seatings

end

class PlaneSeating < ActiveRecord::Base

  belongs_to :plane
  belongs_to :seating
  has_many :plane_seating_notes, dependent: :destroy
  has_many :notes, through: :plane_seating_notes

end

class PlaneSeatingNote < ActiveRecord::Base

  belongs_to :plane_seating
  has_one :note

end

class Note < ActiveRecord::Base
end

Now, this will give me the ability to say Plane.all.first.plan_seatings.first.notes and get the notes I believe. However, I'd like to be able to say Plane.all.first.seatings.notes and get the notes associated with that plane given that seating.

My thought is there should be a way to say, in Plane:

has_many :seatings, through: plane_seating, has_many :notes, through: plane_seating

or some other chaining magic to get a seating with some notes that only apply to that plane and seating combo. a :with, if you will. But I can't think of any syntax that would give me that. Anyone know?

Upvotes: 2

Views: 1442

Answers (2)

steventnorris
steventnorris

Reputation: 5896

I used a helper method in the Plane model to get what I wanted. This method may be inefficient if you are dealing with large datasets, but for my datasets, it works fine. It packages up each seating subset for each plane with the notes associated with it into a hash.

  #Get an easy to read hash of the seatings with their notes
  def seatings_with_notes
    @seatings_with_notes = []
    self.plane_seatings.each do |item|
      seating = Seating.where(id: item.product_application_id).first
      notes = item.notes
      combo = {seating:seating, notes:notes}
      @seatings_with_notes.append(combo)
    end
    return @seatings_with_notes
  end

Upvotes: 0

James Daniels
James Daniels

Reputation: 6981

The best is to pivot it the other way, it you want to grab the notes for a certain Plane:

Note.joins(plane_seating_note: [:plane_seating]).where(plane_seating_note: {plane_seating: {plane_id: 1})

You could make that a scope if you're using it in multiple places and if you want it on the Plane model:

class Plane < ActiveRecord::Base

  has_many :plane_seatings, dependent: :destroy
  has_many :seatings, through: :plane_seatings

  def notes
    @notes ||= Note.for_plane_id id
  end

end

class Note < ActiveRecord::Base

  has_many :plane_seating_notes

  scope :for_plane_id ->(plane_id) { joins(plane_seating_notes: [:plane_seating]).where(plane_seating_notes: {plane_seating: {plane_id: plane_id}) }

end

For a specific seat on a specific plane, you'd typically see something like this in a controller:

@seat  = PlaneSeat.find params[:id]
@plane = @seat.plane
@notes = Note.joins(:plane_seating_notes).where(plane_seating_notes: {plane_seating_id: @seat.id})

But since you have a HMT you could just do

@seat  = PlaneSeat.find params[:id]
@plane = @seat.plane
@notes = @seat.notes

A couple "Rails-way" notes:

  1. Unless you are using Note elsewhere, you should just skip the plane_seat_notes.
  2. Consider using has_and_belongs_to_many if you aren't appending any extra meta-data in the intermediate table; this makes relationships easier and gives you shallower query helpers
  3. Consider using polymorphic relationships rather than unnecessary join tables

Upvotes: 2

Related Questions