posts: refactor modules to use concerning.

This commit is contained in:
evazion
2022-04-06 19:31:09 -05:00
parent a4d43ae72a
commit c707190bc1

View File

@@ -108,9 +108,7 @@ class Post < ApplicationRecord
) )
end end
module FileMethods concerning :FileMethods do
extend ActiveSupport::Concern
def seo_tags def seo_tags
presenter.humanized_essential_tag_string.gsub(/[^a-z0-9]+/, "_").gsub(/(?:^_+)|(?:_+$)/, "").gsub(/_{2,}/, "_") presenter.humanized_essential_tag_string.gsub(/[^a-z0-9]+/, "_").gsub(/(?:^_+)|(?:_+$)/, "").gsub(/_{2,}/, "_")
end end
@@ -191,7 +189,7 @@ class Post < ApplicationRecord
end end
end end
module ImageMethods concerning :ImageMethods do
def twitter_card_supported? def twitter_card_supported?
image_width.to_i >= 280 && image_height.to_i >= 150 image_width.to_i >= 280 && image_height.to_i >= 150
end end
@@ -248,7 +246,7 @@ class Post < ApplicationRecord
end end
end end
module ApprovalMethods concerning :ApprovalMethods do
def in_modqueue? def in_modqueue?
is_pending? || is_flagged? || is_appealed? is_pending? || is_flagged? || is_appealed?
end end
@@ -280,7 +278,7 @@ class Post < ApplicationRecord
end end
end end
module PresenterMethods concerning :PresenterMethods do
def presenter def presenter
@presenter ||= PostPresenter.new(self) @presenter ||= PostPresenter.new(self)
end end
@@ -320,7 +318,7 @@ class Post < ApplicationRecord
end end
end end
module TagMethods concerning :TagMethods do
def tag_array def tag_array
tag_string.split tag_string.split
end end
@@ -683,7 +681,7 @@ class Post < ApplicationRecord
end end
end end
module FavoriteMethods concerning :FavoriteMethods do
def favorited_by?(user) def favorited_by?(user)
return false if user.is_anonymous? return false if user.is_anonymous?
Favorite.exists?(post: self, user: user) Favorite.exists?(post: self, user: user)
@@ -700,7 +698,7 @@ class Post < ApplicationRecord
end end
end end
module PoolMethods concerning :PoolMethods do
def pools def pools
Pool.where("pools.post_ids && array[?]", id) Pool.where("pools.post_ids && array[?]", id)
end end
@@ -716,7 +714,7 @@ class Post < ApplicationRecord
end end
end end
module VoteMethods concerning :VoteMethods do
def vote!(score, voter) def vote!(score, voter)
# Ignore vote if user doesn't have permission to vote. # Ignore vote if user doesn't have permission to vote.
return unless Pundit.policy!(voter, PostVote).create? return unless Pundit.policy!(voter, PostVote).create?
@@ -728,7 +726,7 @@ class Post < ApplicationRecord
end end
end end
module ParentMethods concerning :ParentMethods do
# A parent has many children. A child belongs to a parent. # A parent has many children. A child belongs to a parent.
# A parent cannot have a parent. # A parent cannot have a parent.
# #
@@ -799,7 +797,7 @@ class Post < ApplicationRecord
end end
end end
module DeletionMethods concerning :DeletionMethods do
def expunge! def expunge!
transaction do transaction do
Post.without_timeout do Post.without_timeout do
@@ -850,7 +848,7 @@ class Post < ApplicationRecord
end end
end end
module VersionMethods concerning :VersionMethods do
def create_version(force = false) def create_version(force = false)
if new_record? || saved_change_to_watched_attributes? || force if new_record? || saved_change_to_watched_attributes? || force
create_new_version create_new_version
@@ -888,7 +886,7 @@ class Post < ApplicationRecord
end end
end end
module NoteMethods concerning :NoteMethods do
def has_notes? def has_notes?
last_noted_at.present? last_noted_at.present?
end end
@@ -930,7 +928,7 @@ class Post < ApplicationRecord
end end
end end
module ApiMethods concerning :ApiMethods do
def legacy_attributes def legacy_attributes
hash = { hash = {
"has_comments" => last_commented_at.present?, "has_comments" => last_commented_at.present?,
@@ -973,186 +971,188 @@ class Post < ApplicationRecord
end end
end end
module SearchMethods concerning :SearchMethods do
# Return a set of up to N random posts. May return less if there aren't class_methods do
# enough posts. # Return a set of up to N random posts. May return less if there aren't
# # enough posts.
# @param n [Integer] The maximum number of posts to return #
# @return [ActiveRecord::Relation<Post>] # @param n [Integer] The maximum number of posts to return
def random(n = 1) # @return [ActiveRecord::Relation<Post>]
posts = n.times.map do def random(n = 1)
key = SecureRandom.hex(16) posts = n.times.map do
random_up(key) || random_down(key) key = SecureRandom.hex(16)
end.compact.uniq random_up(key) || random_down(key)
end.compact.uniq
reorder(nil).in_order_of(:id, posts.map(&:id)) reorder(nil).in_order_of(:id, posts.map(&:id))
end
def random_up(key)
where("md5 < ?", key).reorder(md5: :desc).first
end
def random_down(key)
where("md5 >= ?", key).reorder(md5: :asc).first
end
def sample(query, sample_size)
user_tag_match(query, safe_mode: false, hide_deleted_posts: false).reorder(:md5).limit(sample_size)
end
# unflattens the tag_string into one tag per row.
def with_unflattened_tags
joins("CROSS JOIN unnest(string_to_array(tag_string, ' ')) AS tag")
end
def with_comment_stats
relation = left_outer_joins(:comments).group(:id).select("posts.*")
relation = relation.select("COUNT(comments.id) AS comment_count")
relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = TRUE) AS deleted_comment_count")
relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = FALSE) AS active_comment_count")
relation
end
def with_note_stats
relation = left_outer_joins(:notes).group(:id).select("posts.*")
relation = relation.select("COUNT(notes.id) AS note_count")
relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = TRUE) AS active_note_count")
relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = FALSE) AS deleted_note_count")
relation
end
def with_flag_stats
relation = left_outer_joins(:flags).group(:id).select("posts.*")
relation.select("COUNT(post_flags.id) AS flag_count")
end
def with_appeal_stats
relation = left_outer_joins(:appeals).group(:id).select("posts.*")
relation = relation.select("COUNT(post_appeals.id) AS appeal_count")
relation
end
def with_approval_stats
relation = left_outer_joins(:approvals).group(:id).select("posts.*")
relation = relation.select("COUNT(post_approvals.id) AS approval_count")
relation
end
def with_replacement_stats
relation = left_outer_joins(:replacements).group(:id).select("posts.*")
relation = relation.select("COUNT(post_replacements.id) AS replacement_count")
relation
end
def with_child_stats
relation = left_outer_joins(:children).group(:id).select("posts.*")
relation = relation.select("COUNT(children_posts.id) AS child_count")
relation = relation.select("COUNT(children_posts.id) FILTER (WHERE children_posts.is_deleted = TRUE) AS deleted_child_count")
relation = relation.select("COUNT(children_posts.id) FILTER (WHERE children_posts.is_deleted = FALSE) AS active_child_count")
relation
end
def with_pool_stats
pool_posts = Pool.joins("CROSS JOIN unnest(post_ids) AS post_id").select(:id, :is_deleted, :category, "post_id")
relation = joins("LEFT OUTER JOIN (#{pool_posts.to_sql}) pools ON pools.post_id = posts.id").group(:id).select("posts.*")
relation = relation.select("COUNT(pools.id) AS pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.is_deleted = TRUE) AS deleted_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.is_deleted = FALSE) AS active_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.category = 'series') AS series_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.category = 'collection') AS collection_pool_count")
relation
end
def with_queued_at
relation = group(:id)
relation = relation.left_outer_joins(:flags, :appeals)
relation = relation.select("posts.*")
relation = relation.select(Arel.sql("MAX(GREATEST(posts.created_at, post_flags.created_at, post_appeals.created_at)) AS queued_at"))
relation
end
def with_stats(tables)
return all if tables.empty?
relation = all
tables.each do |table|
relation = relation.send("with_#{table}_stats")
end end
from(relation.arel.as("posts")) def random_up(key)
end where("md5 < ?", key).reorder(md5: :desc).first
def available_for_moderation(user, hidden: false)
return none if user.is_anonymous?
approved_posts = user.post_approvals.select(:post_id)
disapproved_posts = user.post_disapprovals.select(:post_id)
if hidden.present?
where("posts.uploader_id = ? OR posts.id IN (#{approved_posts.to_sql}) OR posts.id IN (#{disapproved_posts.to_sql})", user.id)
else
where.not(uploader: user).where.not(id: approved_posts).where.not(id: disapproved_posts)
end
end
def raw_tag_match(tag)
Post.where_array_includes_all("string_to_array(posts.tag_string, ' ')", [tag])
end
# Perform a tag search as an anonymous user. No tag limit is enforced.
def anon_tag_match(query)
user_tag_match(query, User.anonymous, tag_limit: nil, safe_mode: false, hide_deleted_posts: false)
end
# Perform a tag search as the system user, DanbooruBot. The search will
# have moderator-level permissions. No tag limit is enforced.
def system_tag_match(query)
user_tag_match(query, User.system, tag_limit: nil, safe_mode: false, hide_deleted_posts: false)
end
# Perform a tag search as the current user, or as another user.
#
# @param query [String] the tag search to perform
# @param user [User] the user to perform the search as
# @param tag_limit [Integer] the maximum number of tags allowed per search.
# An exception will be raised if the search has too many tags.
# @param safe_mode [Boolean] if true, automatically add rating:s to the search
# @param hide_deleted_posts [Boolean] if true, automatically add -status:deleted to the search
# @return [ActiveRecord::Relation<Post>] the set of resulting posts
def user_tag_match(query, user = CurrentUser.user, tag_limit: user.tag_query_limit, safe_mode: CurrentUser.safe_mode?, hide_deleted_posts: user.hide_deleted_posts?)
post_query = PostQueryBuilder.new(query, user, tag_limit: tag_limit, safe_mode: safe_mode, hide_deleted_posts: hide_deleted_posts)
post_query.normalized_query.build
end
def search(params)
q = search_attributes(
params,
:id, :created_at, :updated_at, :rating, :source, :pixiv_id, :fav_count,
:score, :up_score, :down_score, :md5, :file_ext, :file_size, :image_width,
:image_height, :tag_count, :has_children, :has_active_children,
:is_pending, :is_flagged, :is_deleted, :is_banned,
:last_comment_bumped_at, :last_commented_at, :last_noted_at,
:uploader_ip_addr, :uploader, :approver, :parent,
:artist_commentary, :flags, :appeals, :notes, :comments, :children,
:approvals, :replacements, :pixiv_ugoira_frame_data
)
if params[:tags].present?
q = q.user_tag_match(params[:tags])
end end
if params[:order].present? def random_down(key)
q = PostQueryBuilder.new(nil).search_order(q, params[:order]) where("md5 >= ?", key).reorder(md5: :asc).first
else
q = q.apply_default_order(params)
end end
q def sample(query, sample_size)
user_tag_match(query, safe_mode: false, hide_deleted_posts: false).reorder(:md5).limit(sample_size)
end
# unflattens the tag_string into one tag per row.
def with_unflattened_tags
joins("CROSS JOIN unnest(string_to_array(tag_string, ' ')) AS tag")
end
def with_comment_stats
relation = left_outer_joins(:comments).group(:id).select("posts.*")
relation = relation.select("COUNT(comments.id) AS comment_count")
relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = TRUE) AS deleted_comment_count")
relation = relation.select("COUNT(comments.id) FILTER (WHERE comments.is_deleted = FALSE) AS active_comment_count")
relation
end
def with_note_stats
relation = left_outer_joins(:notes).group(:id).select("posts.*")
relation = relation.select("COUNT(notes.id) AS note_count")
relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = TRUE) AS active_note_count")
relation = relation.select("COUNT(notes.id) FILTER (WHERE notes.is_active = FALSE) AS deleted_note_count")
relation
end
def with_flag_stats
relation = left_outer_joins(:flags).group(:id).select("posts.*")
relation.select("COUNT(post_flags.id) AS flag_count")
end
def with_appeal_stats
relation = left_outer_joins(:appeals).group(:id).select("posts.*")
relation = relation.select("COUNT(post_appeals.id) AS appeal_count")
relation
end
def with_approval_stats
relation = left_outer_joins(:approvals).group(:id).select("posts.*")
relation = relation.select("COUNT(post_approvals.id) AS approval_count")
relation
end
def with_replacement_stats
relation = left_outer_joins(:replacements).group(:id).select("posts.*")
relation = relation.select("COUNT(post_replacements.id) AS replacement_count")
relation
end
def with_child_stats
relation = left_outer_joins(:children).group(:id).select("posts.*")
relation = relation.select("COUNT(children_posts.id) AS child_count")
relation = relation.select("COUNT(children_posts.id) FILTER (WHERE children_posts.is_deleted = TRUE) AS deleted_child_count")
relation = relation.select("COUNT(children_posts.id) FILTER (WHERE children_posts.is_deleted = FALSE) AS active_child_count")
relation
end
def with_pool_stats
pool_posts = Pool.joins("CROSS JOIN unnest(post_ids) AS post_id").select(:id, :is_deleted, :category, "post_id")
relation = joins("LEFT OUTER JOIN (#{pool_posts.to_sql}) pools ON pools.post_id = posts.id").group(:id).select("posts.*")
relation = relation.select("COUNT(pools.id) AS pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.is_deleted = TRUE) AS deleted_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.is_deleted = FALSE) AS active_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.category = 'series') AS series_pool_count")
relation = relation.select("COUNT(pools.id) FILTER (WHERE pools.category = 'collection') AS collection_pool_count")
relation
end
def with_queued_at
relation = group(:id)
relation = relation.left_outer_joins(:flags, :appeals)
relation = relation.select("posts.*")
relation = relation.select(Arel.sql("MAX(GREATEST(posts.created_at, post_flags.created_at, post_appeals.created_at)) AS queued_at"))
relation
end
def with_stats(tables)
return all if tables.empty?
relation = all
tables.each do |table|
relation = relation.send("with_#{table}_stats")
end
from(relation.arel.as("posts"))
end
def available_for_moderation(user, hidden: false)
return none if user.is_anonymous?
approved_posts = user.post_approvals.select(:post_id)
disapproved_posts = user.post_disapprovals.select(:post_id)
if hidden.present?
where("posts.uploader_id = ? OR posts.id IN (#{approved_posts.to_sql}) OR posts.id IN (#{disapproved_posts.to_sql})", user.id)
else
where.not(uploader: user).where.not(id: approved_posts).where.not(id: disapproved_posts)
end
end
def raw_tag_match(tag)
Post.where_array_includes_all("string_to_array(posts.tag_string, ' ')", [tag])
end
# Perform a tag search as an anonymous user. No tag limit is enforced.
def anon_tag_match(query)
user_tag_match(query, User.anonymous, tag_limit: nil, safe_mode: false, hide_deleted_posts: false)
end
# Perform a tag search as the system user, DanbooruBot. The search will
# have moderator-level permissions. No tag limit is enforced.
def system_tag_match(query)
user_tag_match(query, User.system, tag_limit: nil, safe_mode: false, hide_deleted_posts: false)
end
# Perform a tag search as the current user, or as another user.
#
# @param query [String] the tag search to perform
# @param user [User] the user to perform the search as
# @param tag_limit [Integer] the maximum number of tags allowed per search.
# An exception will be raised if the search has too many tags.
# @param safe_mode [Boolean] if true, automatically add rating:s to the search
# @param hide_deleted_posts [Boolean] if true, automatically add -status:deleted to the search
# @return [ActiveRecord::Relation<Post>] the set of resulting posts
def user_tag_match(query, user = CurrentUser.user, tag_limit: user.tag_query_limit, safe_mode: CurrentUser.safe_mode?, hide_deleted_posts: user.hide_deleted_posts?)
post_query = PostQueryBuilder.new(query, user, tag_limit: tag_limit, safe_mode: safe_mode, hide_deleted_posts: hide_deleted_posts)
post_query.normalized_query.build
end
def search(params)
q = search_attributes(
params,
:id, :created_at, :updated_at, :rating, :source, :pixiv_id, :fav_count,
:score, :up_score, :down_score, :md5, :file_ext, :file_size, :image_width,
:image_height, :tag_count, :has_children, :has_active_children,
:is_pending, :is_flagged, :is_deleted, :is_banned,
:last_comment_bumped_at, :last_commented_at, :last_noted_at,
:uploader_ip_addr, :uploader, :approver, :parent,
:artist_commentary, :flags, :appeals, :notes, :comments, :children,
:approvals, :replacements, :pixiv_ugoira_frame_data
)
if params[:tags].present?
q = q.user_tag_match(params[:tags])
end
if params[:order].present?
q = PostQueryBuilder.new(nil).search_order(q, params[:order])
else
q = q.apply_default_order(params)
end
q
end
end end
end end
module PixivMethods concerning :PixivMethods do
def parse_pixiv_id def parse_pixiv_id
self.pixiv_id = nil self.pixiv_id = nil
return unless web_source? return unless web_source?
@@ -1221,7 +1221,7 @@ class Post < ApplicationRecord
end end
end end
module ValidationMethods concerning :ValidationMethods do
def post_is_not_its_own_parent def post_is_not_its_own_parent
if !new_record? && id == parent_id if !new_record? && id == parent_id
errors.add(:base, "Post cannot have itself as a parent") errors.add(:base, "Post cannot have itself as a parent")
@@ -1287,23 +1287,6 @@ class Post < ApplicationRecord
end end
end end
include FileMethods
include ImageMethods
include ApprovalMethods
include PresenterMethods
include TagMethods
include FavoriteMethods
include PoolMethods
include VoteMethods
include ParentMethods
include DeletionMethods
include VersionMethods
include NoteMethods
include ApiMethods
extend SearchMethods
include PixivMethods
include ValidationMethods
has_bit_flags ["has_embedded_notes"] has_bit_flags ["has_embedded_notes"]
def safeblocked? def safeblocked?