3980e9651e
Reviewed-on: #357 Co-authored-by: miteruzo <miteruzo@naver.com> Co-committed-by: miteruzo <miteruzo@naver.com>
120 行
2.8 KiB
Ruby
120 行
2.8 KiB
Ruby
class TheatrePostSelector
|
|
Candidate = Struct.new(:post, :weight, :penalty, :tags, keyword_init: true)
|
|
ELIGIBLE_POST_URL_CONDITION =
|
|
["url LIKE '%nicovideo.jp%'",
|
|
"url LIKE '%youtube.com/watch%'",
|
|
"url LIKE '%youtu.be/%'"]
|
|
.join(' OR ')
|
|
|
|
def initialize theatre:
|
|
@theatre = theatre
|
|
end
|
|
|
|
def select
|
|
candidates = weighted_candidates
|
|
return nil if candidates.empty?
|
|
|
|
total = candidates.sum(&:weight)
|
|
target = rand * total
|
|
|
|
candidates.each do |candidate|
|
|
target -= candidate.weight
|
|
return candidate.post if target <= 0
|
|
end
|
|
|
|
candidates.last.post
|
|
end
|
|
|
|
def weight_json limit: 20
|
|
candidates = weighted_candidates
|
|
sorted = candidates.sort_by { |candidate| [candidate.weight, candidate.post.id] }
|
|
|
|
{ tag_penalties: tag_penalty_json,
|
|
lightest_posts: post_weight_json(sorted.first(limit)),
|
|
heaviest_posts: post_weight_json(sorted.reverse.first(limit)) }
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :theatre
|
|
|
|
def weighted_candidates
|
|
@weighted_candidates ||= begin
|
|
penalties = tag_penalties
|
|
posts = eligible_posts.includes(tags: :tag_name).to_a
|
|
|
|
posts.map do |post|
|
|
post_tags = post.tags.to_a
|
|
penalty = post_tags.sum { |tag| penalties[tag.id].to_i }
|
|
|
|
Candidate.new(
|
|
post:,
|
|
penalty:,
|
|
tags: post_tags,
|
|
weight: 1.0 / (1.0 + penalty))
|
|
end
|
|
end
|
|
end
|
|
|
|
def eligible_posts
|
|
posts = Post.where(ELIGIBLE_POST_URL_CONDITION)
|
|
posts = posts.where.not(id: theatre.current_post_id) if theatre.current_post_id
|
|
posts
|
|
end
|
|
|
|
def active_user_ids
|
|
@active_user_ids ||= theatre.watching_users.ids
|
|
end
|
|
|
|
def tag_penalties
|
|
@tag_penalties ||=
|
|
if active_user_ids.empty?
|
|
{}
|
|
else
|
|
TheatreSkipEventVoter
|
|
.joins(theatre_skip_event: :event_tags)
|
|
.where(user_id: active_user_ids)
|
|
.group('theatre_skip_event_tags.tag_id')
|
|
.count
|
|
end
|
|
end
|
|
|
|
def tag_penalty_json
|
|
return [] if tag_penalties.empty?
|
|
|
|
tags = Tag.where(id: tag_penalties.keys).includes(:tag_name).index_by(&:id)
|
|
|
|
tag_penalties
|
|
.map { |tag_id, penalty|
|
|
tag = tags[tag_id]
|
|
next unless tag
|
|
|
|
{ tag: light_tag_json(tag),
|
|
penalty: }
|
|
}
|
|
.compact
|
|
.sort_by { |row| [-row[:penalty], row[:tag][:name].to_s] }
|
|
end
|
|
|
|
def post_weight_json candidates
|
|
candidates.map { |candidate|
|
|
{ post: light_post_json(candidate.post),
|
|
weight: candidate.weight,
|
|
penalty: candidate.penalty,
|
|
tags: candidate.tags.map { |tag| light_tag_json(tag) } }
|
|
}
|
|
end
|
|
|
|
def light_post_json post
|
|
{ id: post.id,
|
|
title: post.title,
|
|
url: post.url }
|
|
end
|
|
|
|
def light_tag_json tag
|
|
{ id: tag.id,
|
|
name: tag.name,
|
|
category: tag.category }
|
|
end
|
|
end
|