Files
danbooru/app/models/forum_post.rb
evazion b4ce2d83a6 models: remove belongs_to_creator macro.
The belongs_to_creator macro was used to initialize the creator_id field
to the CurrentUser. This made tests complicated because it meant you had
to create and set the current user every time you wanted to create an
object, when lead to the current user being set over and over again. It
also meant you had to constantly be aware of what the CurrentUser was in
many different contexts, which was often confusing. Setting creators
explicitly simplifies everything greatly.
2020-01-21 00:09:38 -06:00

227 lines
6.7 KiB
Ruby

class ForumPost < ApplicationRecord
include Mentionable
attr_readonly :topic_id
belongs_to :creator, class_name: "User"
belongs_to_updater
belongs_to :topic, :class_name => "ForumTopic"
has_many :dtext_links, as: :model, dependent: :destroy
has_many :moderation_reports, as: :model
has_many :votes, class_name: "ForumPostVote"
has_one :tag_alias
has_one :tag_implication
has_one :bulk_update_request
before_validation :initialize_is_deleted, :on => :create
before_save :update_dtext_links, if: :dtext_links_changed?
after_create :update_topic_updated_at_on_create
after_update :update_topic_updated_at_on_update_for_original_posts
after_destroy :update_topic_updated_at_on_destroy
validates_presence_of :body
validate :validate_topic_is_unlocked
validate :validate_post_is_not_spam, on: :create
validate :topic_is_not_restricted, :on => :create
before_destroy :validate_topic_is_unlocked
after_save :delete_topic_if_original_post
after_update(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec|
ModAction.log("#{CurrentUser.name} updated forum ##{rec.id}", :forum_post_update)
end
after_destroy(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec|
ModAction.log("#{CurrentUser.name} deleted forum ##{rec.id}", :forum_post_delete)
end
mentionable(
:message_field => :body,
:title => ->(user_name) {%{#{creator.name} mentioned you in topic ##{topic_id} (#{topic.title})}},
:body => ->(user_name) {%{@#{creator.name} mentioned you in topic ##{topic_id} ("#{topic.title}":[/forum_topics/#{topic_id}?page=#{forum_topic_page}]):\n\n[quote]\n#{DText.excerpt(body, "@" + user_name)}\n[/quote]\n}}
)
module SearchMethods
def topic_title_matches(title)
joins(:topic).merge(ForumTopic.search(title_matches: title))
end
def active
where("forum_posts.is_deleted = false")
end
def permitted
joins(:topic).where("forum_topics.min_level <= ?", CurrentUser.level)
end
def search(params)
q = super
q = q.permitted
q = q.search_attributes(params, :creator, :updater, :topic_id, :is_deleted, :body)
q = q.text_attribute_matches(:body, params[:body_matches], index_column: :text_index)
if params[:linked_to].present?
q = q.where(id: DtextLink.forum_post.wiki_link.where(link_target: params[:linked_to]).select(:model_id))
end
if params[:topic_title_matches].present?
q = q.topic_title_matches(params[:topic_title_matches])
end
if params[:topic_category_id].present?
q = q.joins(:topic).where("forum_topics.category_id = ?", params[:topic_category_id].to_i)
end
q.apply_default_order(params)
end
end
module ApiMethods
def html_data_attributes
super + [[:topic, :is_deleted?]]
end
end
extend SearchMethods
include ApiMethods
def self.new_reply(params)
if params[:topic_id]
new(:topic_id => params[:topic_id])
elsif params[:post_id]
forum_post = ForumPost.find(params[:post_id])
forum_post.build_response
else
new
end
end
def tag_change_request
bulk_update_request || tag_alias || tag_implication
end
def reportable_by?(user)
user.is_builder? && creator_id != user.id && !creator.is_moderator?
end
def votable?
TagAlias.where(forum_post_id: id).exists? ||
TagImplication.where(forum_post_id: id).exists? ||
BulkUpdateRequest.where(forum_post_id: id).exists?
end
def voted?(user, score)
votes.where(creator_id: user.id, score: score).exists?
end
def validate_post_is_not_spam
errors[:base] << "Failed to create forum post" if SpamDetector.new(self, user_ip: CurrentUser.ip_addr).spam?
end
def validate_topic_is_unlocked
return if creator.is_moderator?
return if topic.nil?
if topic.is_locked?
errors[:topic] << "is locked"
throw :abort
end
end
def topic_is_not_restricted
if topic && !topic.visible?(creator)
errors[:topic] << "is restricted"
end
end
def editable_by?(user)
(creator_id == user.id || user.is_moderator?) && visible?(user)
end
def visible?(user, show_deleted_posts = false)
user.is_moderator? || (topic.visible?(user) && (show_deleted_posts || !is_deleted?))
end
def update_topic_updated_at_on_create
if topic
# need to do this to bypass the topic's original post from getting touched
ForumTopic.where(:id => topic.id).update_all(["updater_id = ?, response_count = response_count + 1, updated_at = ?", creator.id, Time.now])
topic.response_count += 1
end
end
def update_topic_updated_at_on_update_for_original_posts
if is_original_post?
topic.touch
end
end
def delete!
update(is_deleted: true)
update_topic_updated_at_on_delete
end
def undelete!
update(is_deleted: false)
update_topic_updated_at_on_undelete
end
def dtext_links_changed?
body_changed? && DText.dtext_links_differ?(body, body_was)
end
def update_dtext_links
self.dtext_links = DtextLink.new_from_dtext(body)
end
def update_topic_updated_at_on_delete
max = ForumPost.where(:topic_id => topic.id, :is_deleted => false).order("updated_at desc").first
if max
ForumTopic.where(:id => topic.id).update_all(["updated_at = ?, updater_id = ?", max.updated_at, max.updater_id])
end
end
def update_topic_updated_at_on_undelete
if topic
ForumTopic.where(:id => topic.id).update_all(["updater_id = ?, updated_at = ?", CurrentUser.id, Time.now])
end
end
def update_topic_updated_at_on_destroy
max = ForumPost.where(:topic_id => topic.id, :is_deleted => false).order("updated_at desc").first
if max
ForumTopic.where(:id => topic.id).update_all(["response_count = response_count - 1, updated_at = ?, updater_id = ?", max.updated_at, max.updater_id])
else
ForumTopic.where(:id => topic.id).update_all("response_count = response_count - 1")
end
topic.response_count -= 1
end
def initialize_is_deleted
self.is_deleted = false if is_deleted.nil?
end
def quoted_response
DText.quote(body, creator.name)
end
def forum_topic_page
(ForumPost.where("topic_id = ? and created_at <= ?", topic_id, created_at).count / Danbooru.config.posts_per_page.to_f).ceil
end
def is_original_post?(original_post_id = nil)
if original_post_id
return id == original_post_id
else
ForumPost.exists?(["id = ? and id = (select _.id from forum_posts _ where _.topic_id = ? order by _.id asc limit 1)", id, topic_id])
end
end
def delete_topic_if_original_post
if is_deleted? && is_original_post?
topic.update_attribute(:is_deleted, true)
end
end
def build_response
dup.tap do |x|
x.body = x.quoted_response
end
end
end