class TheatrePostSelector Candidate = Struct.new(:post, :weight, :penalty, :tags, keyword_init: true) 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("url LIKE '%nicovideo.jp%'") 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 } end end