class Message < ApplicationRecord include Attachment, Broadcasts, Mentionee, Pagination, Searchable belongs_to :room, touch: true belongs_to :creator, class_name: "User", default: -> { Current.user } has_many :boosts, dependent: :destroy has_rich_text :body before_create -> { self.client_message_id ||= Random.uuid } # Bots don't care # Run after Action Text and Active Storage autosave, while creation is still atomic. after_save :record_creation, if: :previously_new_record? after_create_commit -> { room.push_later(self) } scope :ordered, -> { order(:created_at) } scope :with_creator, -> { preload(creator: :avatar_attachment) } scope :with_attachment_details, -> { with_rich_text_body_and_embeds.with_attached_attachment .includes(attachment_blob: :variant_records) } scope :with_boosts, -> { includes(boosts: { booster: :avatar_attachment }) } scope :with_presentation, -> { with_creator.with_attachment_details.with_boosts.preload(:room) } def plain_text_body content = body.body text = if content && content.fragment.find_all(ActionText::Attachment.tag_name).empty? # ActionText::Content#to_plain_text duplicates the fragment to replace # attachments. Without them, reuse its native Fragment conversion instead. content.fragment.to_plain_text.dup else body.to_plain_text end text.presence || attachment&.filename&.to_s || "" end def content_type case when attachment? then "attachment" when sound.present? then "sound" else "text" end.inquiry end def sound plain_text_body.match(/\A\/play (?\w+)\z/) do |match| Sound.find_by_name match[:name] end end private def record_creation create_in_index room.unread_memberships(self) end end