class Node < ApplicationRecord MENUS = %i"" COOKIE_POLICY = :cookie_policy SETTINGS = MENUS << COOKIE_POLICY include AncestryWithSortedUrl include HasAttachments include HasTags has_many :answers, dependent: :destroy extend Mobility translates :slug, :tags, locale_accessors: I18n.available_locales, fallbacks: {zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en} translates :title, :url, :href, :page_title, :page_description, locale_accessors: I18n.available_locales, fallbacks: {zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en} NODE_TEMPLATES = %w"tmpl_article tmpl_index tmpl_list" enum :status, { status_published: 0, status_draft: 1, status_archived: 2 } enum :template, { tmpl_index: 0, tmpl_article: 1, tmpl_list: 2 } include PgSearch::Model pg_search_scope :pg_search, against: {title: 'A', url: 'B', page_title: 'A', page_description: 'B', href: 'B', slug: 'B' }, associated_against: { attachments: [:body, :url] } before_validation :remove_empty_tags validates_presence_of :title validates :expires_at, date: { after: :published_at }, allow_nil: true scope :ordered, -> { order(position: :asc) } scope :by_title, ->(rev) { order(Arel.sql(rev ? "title->>'#{I18n.locale.to_s}' DESC, id DESC": "title ->>'#{I18n.locale.to_s}' ASC, id ASC")) } scope :by_status, ->(rev) { order(status: rev ? :desc : :asc, id: rev ? :desc : :asc) } scope :by_slug, ->(rev) { order(ancestry: rev ? :desc : :asc, position: rev ? :desc : :asc, id: rev ? :desc : :asc) } scope :by_last_modified, ->(rev) { order(updated_at: rev ? :asc : :desc, id: rev ? :desc : :asc) } # scope :not_excluded, -> { where "NOT(? = ANY (excluded_locales))", I18n.locale.to_s } scope :for_current_locale, -> { where("(cardinality(excluded_locales) = 0 AND is_allowlist = false) OR (is_allowlist = true AND ? = ANY(excluded_locales)) OR (is_allowlist = false AND NOT(? = ANY(excluded_locales)))", I18n.locale.to_s, I18n.locale.to_s ) } scope :viewable, -> { status_published.for_current_locale.where('published_at <= ? AND (expires_at IS NULL OR expires_at > ?)', Time.current, Time.current) } scope :of_template, ->(tmpl) { where(template: Node.templates[tmpl.to_s]) } scope :with_setting, ->(setting) { setting.kind_of?(Array) ? where("settings && ?", "{#{setting.join(',')}}") : where("settings @> ?", "{#{setting}}") } scope :simple_search, ->(q) { pg_search(q) unless q.blank? } scope :tiles, -> { where("ancestry LIKE '/?/%'", Node.where(position: 2, ancestry_depth: 0).first) } def self.tags result = {} I18n.available_locales.each do |locale| key = locale.to_s.downcase.sub('-','_') result[key] = pluck(Arel.sql("tags->'#{locale}'")).flatten.compact.uniq.sort.reject{|v| v.blank?} end result end def self.categories ['document'] end def category self.children.any? ? 'folder' : 'document' end def document? self.category == 'document' end def href_or_url href.present?? href : url end def viewable? Node.viewable.where(id: self.path_ids).count == self.path_ids.length end def index? self.attachments.blank? end def site? (self.root || self).position == 1 end def tile? (self.root || self).position == 2 end def node_type return "site" if self.site? 'tile' end private def remove_empty_tags %w"settings excluded_locales tags_en tags_zh tags_hr tags_cs tags_da tags_nl tags_fi tags_fr tags_fr_ca tags_de tags_hu tags_it tags_ja tags_ko tags_nb tags_pl tags_pt tags_ro tags_sr tags_sk tags_sl tags_es tags_sv tags_uk ".map do |k| self.send "#{k}=", self.send(k).reject { |v| v.blank? } unless self.send(k).blank? end end end