Home > Net >  Get most recent records from deeply nested model
Get most recent records from deeply nested model

Time:12-31

Say I have 3 models:

ModelA has many ModelB ModelB has many ModelC

I'm querying ModelA, but in ModelC I have multiple ones of the same type, let's say I have 3 but I only need the most recently one.

I tried to do something like this...

records = ModelA.where(some query).includes ModelB includes ModelC

// convert activerecord collection to array
records = records.to_a

records.each do |record|

  record.modelBs.each do |modelB|
    filter the modelCs i don't need

    modelB.modelCs = filteredModelCs

  end

end

return records

but instead of merely returning the array of records, an UPDATE sql query is run and the db records are modified. this is a surprise because i never used the .save method and i thought i had converted the collection from an active record collection to an array

How can I filter deeply nested records without modifying the db records? then i can return the filtered result

CodePudding user response:

Assigning a list of instances to a has_many collection with = will immediately persist the changes to the database.

Instead, I would try to solve this with more specific associations like this:

class A
  has_many :bs
  has_many(:cs, through: :bs)
  has_one :recent_c, -> { order(created_at: :desc).limit(1) }, source: :cs

class B
  has_many :cs

With those associations, I would expect the following to work:

as = A.where(some query).includes(:recent_c)

as.each do |a|
  a.recent_c # returns the most recent c for this a
end

CodePudding user response:

If I got you right, you want to get a collection of latest Cs, which are connected to Bs, which are connected to certain A-relation? If so, you can do something like that (considering you have tables as, bs and cs):

class A < ApplicationRecord
  has_many :bs
end

class B < ApplicationRecord
  belongs_to :a

  has_many :cs
end

class C < ApplicationRecord
  belongs_to :b

  scope :recent_for_bs, -> { joins(
    <<-sql
      INNER JOIN (SELECT b_id, MAX(id) AS max_id FROM cs GROUP BY b_id) recent_cs
      ON cs.b_id = recent_cs.b_id AND cs.id = recent_cs.max_id
    sql
  ) }
end

And then you would query Cs like that:

C.recent_for_bs.joins(b: :a).merge(A.where(some_query))

You get recent Cs, inner join them with Bs and As and then get records connected to your A-relation by merging it.

  • Related