Skip to content

Instantly share code, notes, and snippets.

@stevenharman
Created January 13, 2016 19:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevenharman/63f5f07672bd7893b3d6 to your computer and use it in GitHub Desktop.
Save stevenharman/63f5f07672bd7893b3d6 to your computer and use it in GitHub Desktop.
Trying to avoid an N+1 query when we only want the first record of an ordered ActiveRecord association.
class Organization < ActiveRecord::Base
has_many :people, dependent: :destroy, inverse_of: :organization
has_many :locations, dependent: :destroy
has_many :activity_items, -> { order(last_activity_at: :desc) }, dependent: :destroy
has_one :last_activity, -> { order(last_activity_at: :desc) }, class_name: 'ActivityItem'
end
class ActivityItem < ActiveRecord::Base
belongs_to :organization
belongs_to :user
belongs_to :item, polymorphic: true
end
# item is one of Email::Message, PhoneCall, or Note
module Email
class Message < ActiveRecord::Base
belongs_to :thread, class_name: 'Email::Thread'
belongs_to :user
has_many :activity_items, as: :item, dependent: :destroy
end
end
class PhoneCall < ActiveRecord::Base
belongs_to :user
has_many :activity_items, as: :item, dependent: :destroy
end
class Note < ActiveRecord::Base
belongs_to :user
has_many :activity_items, as: :item, dependent: :destroy
end
# We need to load a list of Organizations, and each org's: Locations, People + ContactMethod, and last ActivityItem + Item (email, note, call).
# The killer here is that `last_activity` relation, which is just the most recent `ActivityItem` plus its associated Item.
# The N+1 ends up being the `ActivityItem#item` relationship.
organizations = current_dealership.organizations.
order(id: :desc).
includes(:locations, :last_activity, people: :contact_methods).
@stevenharman
Copy link
Author

Defining the offending has_one association as this seems to do the trick

class Organization < ActiveRecord::Base
  # other associations and scopes...
  has_one :last_activity, -> {
    select('DISTINCT ON (organization_id) *').order(:organization_id, created_at: :desc)
  }, class_name: 'ActivityItem'
  # ... other associations and scopes
end

This works by ordering the activity_items table first by organization_id and then breaks any ordering ties using created_at desc. Then Postgres limits the result set such that organization_id is distinct, which leaves only the "top" result for each organization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment