Skip to content

Instantly share code, notes, and snippets.

@estum
Last active August 29, 2015 14:15
Show Gist options
  • Save estum/4a8da74c39d4711d50b4 to your computer and use it in GitHub Desktop.
Save estum/4a8da74c39d4711d50b4 to your computer and use it in GitHub Desktop.
Eager load has many polymorphic association with limit per each owner
# Standart behavior: if you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects
# This example shows how to eager load limited number of records in collection.
# Usage:
# Post.find(42).likes.size # => 300
# Post.find(42).likes.limit_per_target(5).size # => 5
# Post.preload(:likes).map {|t| t.likes.size } # => [100, 200, 300, ... ]
# Post.preload(:limited_likes).map {|t| t.likes.size } # => [5, 5, 5, ... ]
#
# recommended: postgres ~>9.4, ruby ~>2.2, rails ~>4.2, squeel
# The polymoriphic association model (like) which can be eager-loaded with a specified limit per each owner:
class Like < ActiveRecord::Base
class WithLimitPerTarget < self
default_scope ->{ limit_per_target(5) } # set the limit here
end
belongs_to :target, polymorphic: true
DIR_OP_MAP = { asc: '>', desc: '<' }
# => SELECT "likes".*, count("lx"."id"), "likes".* FROM "likes"
# LEFT OUTER JOIN (SELECT "likes".* FROM "likes") lx
# ON ("likes"."target_id" = "lx"."target_id" AND "likes"."target_type" = "lx"."target_type" AND "likes"."id" < "lx"."id")
# GROUP BY "likes"."id"
# HAVING count("lx"."id") < 5
def self.limit_per_target(lim = 5, dir = :desc)
_gt_or_lt_ = DIR_OP_MAP.fetch(dir)
select( "likes.*, count(lx.id)" ).
joins { Like.all.as('lx'). # joins self to count per owner
on { (target_id == lx.target_id) & (target_type == lx.target_type) & (id.op _gt_or_lt_, lx.id) }.
outer}.
group { id }.
having { count(lx.id) < lim } # use limit
end
# Concern, which should be included into target model
concern :HasLikes do
included do
has_many :likes, as: :target
has_many :limited_likes, as: :target, class_name: "Like::WithLimitPerTarget"
alias_method_chain :likes, :limit
end
def likes_with_limit
loaded_limited_likes? ? limited_likes : likes_without_limit
end
def loaded_limited_likes?
association(:limited_likes).loaded?
end
end
end
# Target model
class Post < ActiveRecord::Base
include Like::HasLikes
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment