|
|
|
@ -1,6 +1,5 @@ |
|
|
|
class Node < ApplicationRecord |
|
|
|
|
|
|
|
MENUS = %i"" |
|
|
|
MENUS = %i[] |
|
|
|
COOKIE_POLICY = :cookie_policy |
|
|
|
SETTINGS = MENUS << COOKIE_POLICY |
|
|
|
|
|
|
|
@ -14,7 +13,7 @@ class Node < ApplicationRecord |
|
|
|
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} |
|
|
|
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, |
|
|
|
@ -22,14 +21,12 @@ class Node < ApplicationRecord |
|
|
|
: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} |
|
|
|
|
|
|
|
|
|
|
|
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 } |
|
|
|
|
|
|
|
enum :status, { status_published: 0, status_draft: 1, status_archived: 2 } |
|
|
|
|
|
|
|
enum :template, { |
|
|
|
tmpl_article: 0, |
|
|
|
tmpl_stage: 0, |
|
|
|
tmpl_chance: 1, |
|
|
|
tmpl_choice: 2, |
|
|
|
tmpl_bonus: 3 |
|
|
|
@ -37,9 +34,9 @@ class Node < ApplicationRecord |
|
|
|
|
|
|
|
include PgSearch::Model |
|
|
|
pg_search_scope :pg_search, |
|
|
|
against: {title: 'A', url: 'B', page_title: 'A', page_description: 'B', href: 'B', slug: 'B' }, |
|
|
|
against: { title: "A", url: "B", page_title: "A", page_description: "B", href: "B", slug: "B" }, |
|
|
|
associated_against: { |
|
|
|
attachments: [:body, :url] |
|
|
|
attachments: [ :body, :url ] |
|
|
|
} |
|
|
|
|
|
|
|
before_validation :remove_empty_tags |
|
|
|
@ -51,7 +48,7 @@ class Node < ApplicationRecord |
|
|
|
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_title, ->(rev) { order(Arel.sql(rev ? "title->>'#{I18n.locale}' DESC, id DESC": "title ->>'#{I18n.locale}' 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) } |
|
|
|
@ -62,10 +59,10 @@ class Node < ApplicationRecord |
|
|
|
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 ) |
|
|
|
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 :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}}") } |
|
|
|
@ -77,25 +74,25 @@ class Node < ApplicationRecord |
|
|
|
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?} |
|
|
|
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'] |
|
|
|
[ "document" ] |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def category |
|
|
|
self.children.any? ? 'folder' : 'document' |
|
|
|
self.children.any? ? "folder" : "document" |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def document? |
|
|
|
self.category == 'document' |
|
|
|
self.category == "document" |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
@ -126,66 +123,16 @@ class Node < ApplicationRecord |
|
|
|
|
|
|
|
def node_type |
|
|
|
return "site" if self.site? |
|
|
|
'tile' |
|
|
|
"tile" |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def answer_percentages |
|
|
|
# Get all valid answers (non-nil values) |
|
|
|
valid_answers = answers.where.not(value: nil) |
|
|
|
|
|
|
|
# Count answers by value |
|
|
|
value_counts = valid_answers.group(:value).count |
|
|
|
|
|
|
|
# Calculate total number of answers |
|
|
|
total_answers = valid_answers.count |
|
|
|
|
|
|
|
# Initialize with 0 for both expected values |
|
|
|
percentages = {0 => 0, 1 => 0} |
|
|
|
|
|
|
|
# Return initial percentages if no answers |
|
|
|
return percentages if total_answers.zero? |
|
|
|
|
|
|
|
# Calculate exact percentages first (not rounded) |
|
|
|
exact_percentages = {} |
|
|
|
value_counts.each do |value, count| |
|
|
|
# Only consider values 0 and 1 |
|
|
|
next unless [0, 1].include?(value) |
|
|
|
exact_percentages[value] = count.to_f / total_answers * 100 |
|
|
|
end |
|
|
|
|
|
|
|
# First round down all percentages |
|
|
|
[0, 1].each do |value| |
|
|
|
percentages[value] = (exact_percentages[value] || 0).floor |
|
|
|
end |
|
|
|
|
|
|
|
# Calculate how much we're off by due to rounding |
|
|
|
remaining = 100 - percentages.values.sum |
|
|
|
|
|
|
|
# Distribute the remaining percentage points to values with the largest fractional parts |
|
|
|
if remaining > 0 |
|
|
|
# Filter exact percentages to only include 0 and 1 |
|
|
|
sorted_by_fraction = exact_percentages |
|
|
|
.select { |k, _| [0, 1].include?(k) } |
|
|
|
.sort_by { |_, p| p - p.floor } |
|
|
|
.reverse |
|
|
|
|
|
|
|
# Add 1 to the values with the largest fractional parts |
|
|
|
sorted_by_fraction.take(remaining).each do |value, _| |
|
|
|
percentages[value] += 1 |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
# Return the percentages hash with keys 0 and 1 |
|
|
|
percentages |
|
|
|
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| |
|
|
|
%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 |