mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 02:02:27 +00:00
This PR addresses several items listed in our rubocop_todo by implementing the necessary corrections and enhancements. As a result, we are now able to remove the rubocop_todo file entirely, streamlining our codebase and ensuring adherence to our coding standards. fixes: https://linear.app/chatwoot/issue/CW-1806/chore-rubocop-audit
173 lines
5.4 KiB
Ruby
173 lines
5.4 KiB
Ruby
# == Schema Information
|
|
#
|
|
# Table name: articles
|
|
#
|
|
# id :bigint not null, primary key
|
|
# content :text
|
|
# description :text
|
|
# meta :jsonb
|
|
# position :integer
|
|
# slug :string not null
|
|
# status :integer
|
|
# title :string
|
|
# views :integer
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# account_id :integer not null
|
|
# associated_article_id :bigint
|
|
# author_id :bigint
|
|
# category_id :integer
|
|
# folder_id :integer
|
|
# portal_id :integer not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_articles_on_associated_article_id (associated_article_id)
|
|
# index_articles_on_author_id (author_id)
|
|
# index_articles_on_slug (slug) UNIQUE
|
|
#
|
|
class Article < ApplicationRecord
|
|
include PgSearch::Model
|
|
|
|
has_many :associated_articles,
|
|
class_name: :Article,
|
|
foreign_key: :associated_article_id,
|
|
dependent: :nullify,
|
|
inverse_of: 'root_article'
|
|
|
|
belongs_to :root_article,
|
|
class_name: :Article,
|
|
foreign_key: :associated_article_id,
|
|
inverse_of: :associated_articles,
|
|
optional: true
|
|
belongs_to :account
|
|
belongs_to :category, optional: true
|
|
belongs_to :portal
|
|
belongs_to :author, class_name: 'User', inverse_of: :articles
|
|
|
|
before_validation :ensure_account_id
|
|
before_validation :ensure_article_slug
|
|
|
|
validates :account_id, presence: true
|
|
validates :author_id, presence: true
|
|
validates :title, presence: true
|
|
validates :content, presence: true
|
|
|
|
# ensuring that the position is always set correctly
|
|
before_create :add_position_to_article
|
|
after_save :category_id_changed_action, if: :saved_change_to_category_id?
|
|
|
|
enum status: { draft: 0, published: 1, archived: 2 }
|
|
|
|
scope :search_by_category_slug, ->(category_slug) { where(categories: { slug: category_slug }) if category_slug.present? }
|
|
scope :search_by_category_locale, ->(locale) { where(categories: { locale: locale }) if locale.present? }
|
|
scope :search_by_author, ->(author_id) { where(author_id: author_id) if author_id.present? }
|
|
scope :search_by_status, ->(status) { where(status: status) if status.present? }
|
|
scope :order_by_updated_at, -> { reorder(updated_at: :desc) }
|
|
scope :order_by_position, -> { reorder(position: :asc) }
|
|
scope :order_by_views, -> { reorder(views: :desc) }
|
|
|
|
# TODO: if text search slows down https://www.postgresql.org/docs/current/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS
|
|
pg_search_scope(
|
|
:text_search,
|
|
against: %i[
|
|
title
|
|
description
|
|
content
|
|
],
|
|
using: {
|
|
tsearch: {
|
|
prefix: true
|
|
}
|
|
}
|
|
)
|
|
|
|
def self.search(params)
|
|
records = joins(
|
|
:category
|
|
).search_by_category_slug(
|
|
params[:category_slug]
|
|
).search_by_category_locale(params[:locale]).search_by_author(params[:author_id]).search_by_status(params[:status])
|
|
|
|
records = records.text_search(params[:query]) if params[:query].present?
|
|
records
|
|
end
|
|
|
|
def associate_root_article(associated_article_id)
|
|
article = portal.articles.find(associated_article_id) if associated_article_id.present?
|
|
|
|
return if article.nil?
|
|
|
|
root_article_id = self.class.find_root_article_id(article)
|
|
|
|
update(associated_article_id: root_article_id) if root_article_id.present?
|
|
end
|
|
|
|
# Make sure we always associate the parent's associated id to avoid the deeper associations od articles.
|
|
def self.find_root_article_id(article)
|
|
article.associated_article_id || article.id
|
|
end
|
|
|
|
def draft!
|
|
update(status: :draft)
|
|
end
|
|
|
|
def increment_view_count
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
update_column(:views, views? ? views + 1 : 1)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
end
|
|
|
|
def self.update_positions(positions_hash)
|
|
positions_hash.each do |article_id, new_position|
|
|
# Find the article by its ID and update its position
|
|
article = Article.find(article_id)
|
|
article.update!(position: new_position)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def category_id_changed_action
|
|
# We need to update the position of the article in the new category
|
|
return unless persisted?
|
|
|
|
# this means the article is just created
|
|
# and the category_id is newly set
|
|
# and the position is already present
|
|
return if created_at_before_last_save.nil? && position.present? && category_id_before_last_save.nil?
|
|
|
|
update_article_position_in_category
|
|
end
|
|
|
|
def add_position_to_article
|
|
# on creation if a position is already present, ignore it
|
|
return if position.present?
|
|
|
|
update_article_position_in_category
|
|
end
|
|
|
|
def update_article_position_in_category
|
|
max_position = Article.where(category_id: category_id, account_id: account_id).maximum(:position)
|
|
|
|
new_position = max_position.present? ? max_position + 10 : 10
|
|
|
|
# update column to avoid validations if the article is already persisted
|
|
if persisted?
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
update_column(:position, new_position)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
else
|
|
self.position = new_position
|
|
end
|
|
end
|
|
|
|
def ensure_account_id
|
|
self.account_id = portal&.account_id
|
|
end
|
|
|
|
def ensure_article_slug
|
|
self.slug ||= "#{Time.now.utc.to_i}-#{title.underscore.parameterize(separator: '-')}" if title.present?
|
|
end
|
|
end
|