loomio/loomio

View on GitHub
app/models/comment.rb

Summary

Maintainability
A
45 mins
Test Coverage
class Comment < ApplicationRecord
  include Discard::Model
  include CustomCounterCache::Model
  include Translatable
  include Reactable
  include HasMentions
  include HasCreatedEvent
  include HasEvents
  include HasRichText
  include Searchable

  def self.pg_search_insert_statement(id: nil, author_id: nil, discussion_id: nil)
    content_str = "regexp_replace(CONCAT_WS(' ', comments.body, users.name), E'<[^>]+>', '', 'gi')"
    <<~SQL.squish
      INSERT INTO pg_search_documents (
        searchable_type,
        searchable_id,
        group_id,
        discussion_id,
        author_id,
        authored_at,
        content,
        ts_content,
        created_at,
        updated_at)
      SELECT 'Comment' AS searchable_type,
        comments.id AS searchable_id,
        discussions.group_id as group_id,
        discussions.id AS discussion_id,
        comments.user_id AS author_id,
        comments.created_at AS authored_at,
        #{content_str} AS content,
        to_tsvector('simple',#{content_str}) as ts_content,
        now() AS created_at,
        now() AS updated_at
      FROM comments
        LEFT JOIN discussions ON discussions.id = comments.discussion_id
        LEFT JOIN users ON users.id = comments.user_id
        #{discussion_id ? "LEFT JOIN events ON events.eventable_type = 'Comment' AND events.eventable_id = comments.id" : ""}
      WHERE comments.discarded_at IS NULL 
        AND discussions.discarded_at IS NULL 
        #{id ? " AND comments.id = #{id.to_i} LIMIT 1" : ""}
        #{author_id ? " AND comments.user_id = #{author_id.to_i}" : ""}
        #{discussion_id ? " AND events.discussion_id = #{discussion_id.to_i}" : ""}
    SQL
  end

  has_paper_trail only: [:body, :body_format, :user_id, :discarded_at, :discarded_by]

  is_translatable on: :body
  is_mentionable  on: :body
  is_rich_text    on: :body

  belongs_to :discussion
  belongs_to :user
  belongs_to :parent, polymorphic: true

  has_many :documents, as: :model, dependent: :destroy

  validates_presence_of :user, unless: :discarded_at

  validate :parent_comment_belongs_to_same_discussion
  validate :has_body_or_attachment

  alias_attribute :author, :user
  alias_attribute :author_id, :user_id

  scope :dangling, -> { joins('left join discussions on discussion_id = discussions.id').where('discussion_id is not null and discussions.id is null') }
  scope :in_organisation, ->(group) { includes(:user, :discussion).joins(:discussion).where("discussions.group_id": group.id_and_subgroup_ids) }

  before_validation :assign_parent_if_nil

  delegate :name, to: :user, prefix: :user
  delegate :name, to: :user, prefix: :author
  delegate :email, to: :user, prefix: :user
  delegate :author, to: :parent, prefix: :parent, allow_nil: true
  delegate :group, to: :discussion
  delegate :group_id, to: :discussion, allow_nil: true
  delegate :full_name, to: :group, prefix: :group
  delegate :locale, to: :user
  delegate :mailer, to: :discussion
  delegate :guests, to: :discussion
  delegate :members, to: :discussion
  delegate :title, to: :discussion

  define_counter_cache(:versions_count) { |comment| comment.versions.count }

  def real_participant
    author
  end

  def assign_parent_if_nil
    self.parent = self.discussion if self.parent_id.nil?
  end

  def poll
    nil
  end

  def poll_id
    nil
  end

  def user
    super || AnonymousUser.new
  end

  def should_pin
    return false if body_format != "html"
    Nokogiri::HTML(self.body).css("h1,h2,h3").length > 0
  end

  def parent_event
    if parent.nil? && discussion.present?
      self.parent = self.discussion
      save!(validate: false)
    end

    if parent.is_a? Stance
      # if stance, the could be updated event. sucks i know
      Event.where(eventable_type: parent_type, eventable_id: parent_id).where('discussion_id is not null').first
    else
      parent.created_event
    end
  end

  def created_event_kind
    :new_comment
  end

  def is_most_recent?
    discussion.comments.last == self
  end

  def is_edited?
    edited_at.present?
  end

  private

  def has_body_or_attachment
    if !discarded_at && body_blank? && files.empty? && image_files.empty?
      errors.add(:body, I18n.t(:"activerecord.errors.messages.blank"))
    end
  end

  def body_blank?
    body.to_s.empty? || body.to_s == "<p></p>"
  end

  def parent_comment_belongs_to_same_discussion
    # if someone replies to a deleted comment (in practice, by email), reparent to the discussion
    self.parent = self.discussion if parent.nil? && discussion.present?

    unless discussion_id == parent.discussion_id
      errors.add(:parent, "Needs to have same discussion id")
    end
  end
end