Last active
August 29, 2015 14:15
-
-
Save estum/4a8da74c39d4711d50b4 to your computer and use it in GitHub Desktop.
Eager load has many polymorphic association with limit per each owner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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