Search code examples
ruby-on-railscachingactiverecordfragment-caching

Active Record query to compute cache key for a view with HABTM associated records?


Student and Parent have a has_and_belongs_to_many relationship, both belong to a School.

This query returns the latest timestamp:

School.first.students.includes(:parents).maximum("parents.updated_at")

Why doesn't this:

School.first.students.left_outer_joins(:parents).select("MAX(parents.updated_at)")

The console log implies that both execute the same SQL:

SELECT MAX(parents.updated_at) FROM "students" LEFT OUTER JOIN "parents_students" ON "parents_students"."student_id" = "students"."id" LEFT OUTER JOIN "parents" ON "parents"."id" = "parents_students"."parent_id" WHERE "students"."school_id" = $1  [["school_id", 1]]

But the first is the only one that returns a timestamp value.

The reason I want to use the 2nd form is that I'd like to be able to do things like:

School.first.students.left_outer_joins(:parents).select('COUNT(students.*) AS student_count, COUNT(parents.*) AS parent_count, MAX(students.updated_at) AS student_latest_update, MAX(parents.updated_at) AS parent_latest_update')`<br>

to compute a cache key for a view with associations. I'm currently using an inefficient array key that performs redundant queries:

<% cache [batch_of_students, batch_of_students.includes(:parents).maximum("parents.updated_at")] do %>

Solution

  • You can do it with pluck.

    School.first.students.left_outer_joins(:parents).pluck("MAX(parents.updated_at)")
    

    You can then create your cache_key with

    cache_key = School.first.students.left_outer_joins(:parents)
      .pluck(%(
        COUNT(students.*),
        COUNT(parents.*), 
        MAX(students.updated_at), 
        MAX(parents.updated_at)))
    

    Which returns an array with those 4 values which can act as your cache key.

    # [100, 100, 2015-08-24 02:14:07 UTC, 2015-08-24 02:14:07 UTC]