diff --git a/Gemfile b/Gemfile index de3879356..186ea6ff4 100644 --- a/Gemfile +++ b/Gemfile @@ -70,10 +70,6 @@ group :development do gem "rubocop-rails-omakase", require: false end -group :test do - gem "launchy" -end - group :development, :test do gem "better_errors" # gem "binding_of_caller" # Temporarily commented - doesn't support Ruby 4.0.1 diff --git a/app/controllers/admin/analytics_controller.rb b/app/controllers/admin/analytics_controller.rb index 4ab57c745..4dd4d8e21 100644 --- a/app/controllers/admin/analytics_controller.rb +++ b/app/controllers/admin/analytics_controller.rb @@ -20,8 +20,8 @@ def index @most_printed_workshops = decorate_with_counts(most_printed_for_model(Workshop, time_scope), :print_count) @most_printed_resources = decorate_with_counts(most_printed_for_model(Resource, time_scope), :print_count) - # @most_printed_community_news = decorate_with_counts(most_printed_for_model(CommunityNews, time_scope), :print_count) - # @most_printed_stories = decorate_with_counts(most_printed_for_model(Story, time_scope), :print_count) + @most_printed_community_news = decorate_with_counts(most_printed_for_model(CommunityNews, time_scope), :print_count) + @most_printed_stories = decorate_with_counts(most_printed_for_model(Story, time_scope), :print_count) # @most_printed_workshop_variations = decorate_with_counts(most_printed_for_model(WorkshopVariation, time_scope), :print_count) # @most_printed_quotes = decorate_with_counts(most_printed_for_model(Quote, time_scope), :print_count) # @most_printed_tutorials = decorate_with_counts(most_printed_for_model(Tutorial, time_scope), :print_count) diff --git a/app/controllers/banners_controller.rb b/app/controllers/banners_controller.rb index 41b01e47f..96a507932 100644 --- a/app/controllers/banners_controller.rb +++ b/app/controllers/banners_controller.rb @@ -58,7 +58,7 @@ def set_banner # Strong parameters def banner_params params.require(:banner).permit( - :content, :show, :created_by_id, :updated_by_id + :content, :published, :created_by_id, :updated_by_id ) end end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index f7d972721..b95ff9673 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -8,7 +8,7 @@ def index unfiltered = Category.includes(:category_type).joins(:category_type) filtered = unfiltered.category_type_id(params[:category_type_id]) .category_name(params[:category_name]) - .published_search(params[:published_search]) + .published(params[:published]) .order(Arel.sql("category_types.name, categories.position, categories.name")) @categories = filtered.paginate(page: params[:page], per_page: per_page) diff --git a/app/controllers/community_news_controller.rb b/app/controllers/community_news_controller.rb index c99c6d348..d963e1dd3 100644 --- a/app/controllers/community_news_controller.rb +++ b/app/controllers/community_news_controller.rb @@ -1,5 +1,6 @@ class CommunityNewsController < ApplicationController include ExternallyRedirectable, AssetUpdatable, AhoyTracking + skip_before_action :authenticate_user!, only: [ :index, :show ] before_action :set_community_news, only: [ :show, :edit, :update, :destroy ] def index diff --git a/app/controllers/concerns/ahoy_tracking.rb b/app/controllers/concerns/ahoy_tracking.rb index de801d2b1..d8fa36d64 100644 --- a/app/controllers/concerns/ahoy_tracking.rb +++ b/app/controllers/concerns/ahoy_tracking.rb @@ -35,16 +35,15 @@ def track_tagging_browse(grouped_results) return if sectors.blank? && categories.blank? - total_results = grouped_results.values.sum { |r| r.respond_to?(:total_entries) ? r.total_entries : r.size } - + # total_results = grouped_results.values.sum { |r| r.respond_to?(:total_entries) ? r.total_entries : r.size } ahoy.track "browse.taggings", { sectors: sectors, categories: categories, - result_count: total_results, - result_breakdown: grouped_results.transform_values do |r| - r.respond_to?(:total_entries) ? r.total_entries : r.size - end, - page_result_count: grouped_results.values.sum(&:size) + # result_count: total_results, + # result_breakdown: grouped_results.transform_values do |r| + # r.respond_to?(:total_entries) ? r.total_entries : r.size + # end, + page_result_count: grouped_results.values.sum(&:size) # only loaded page } end diff --git a/app/controllers/event_registrations_controller.rb b/app/controllers/event_registrations_controller.rb index e3c5a5dfe..6e02e5889 100644 --- a/app/controllers/event_registrations_controller.rb +++ b/app/controllers/event_registrations_controller.rb @@ -74,7 +74,7 @@ def destroy # Optional hooks for setting variables for forms or index def set_form_variables - @events = Event.where(inactive: false).order(:start_date) + @events = Event.where(published: true).order(:start_date) @registrants = User.active.order(:last_name, :first_name) end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index da81eb7ea..53731343f 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,6 +1,6 @@ class EventsController < ApplicationController include AhoyTracking, AssetUpdatable - skip_before_action :authenticate_user!, only: %i[ index show] + skip_before_action :authenticate_user!, only: [ :index, :show ] before_action :set_event, only: %i[ show edit update destroy ] def index @@ -93,7 +93,7 @@ def event_params :featured, :start_date, :end_date, :registration_close_date, - :inactive, + :published, :publicly_visible, :publicly_featured ) diff --git a/app/controllers/facilitators_controller.rb b/app/controllers/facilitators_controller.rb index 55024e94f..fd546f1af 100644 --- a/app/controllers/facilitators_controller.rb +++ b/app/controllers/facilitators_controller.rb @@ -85,7 +85,7 @@ def set_form_variables @facilitator.user.project_users.first || @facilitator.user.project_users.build end projects = if current_user.super_user? - Project.active + Project.published else current_user.projects end @@ -122,6 +122,7 @@ def facilitator_params :profile_show_workshop_variations, :profile_show_workshops, :profile_show_workshop_logs, + :published, :member_since, :linked_in_url, :facebook_url, @@ -142,6 +143,7 @@ def facilitator_params :district, :locality, :phone, + :inactive, :_destroy ], contact_methods_attributes: [ diff --git a/app/controllers/faqs_controller.rb b/app/controllers/faqs_controller.rb index 5c53089c0..734d9fad4 100644 --- a/app/controllers/faqs_controller.rb +++ b/app/controllers/faqs_controller.rb @@ -1,9 +1,10 @@ class FaqsController < ApplicationController + skip_before_action :authenticate_user!, only: [ :index, :show ] before_action :set_faq, only: [ :show, :edit, :update, :destroy ] def index - faqs = current_user.super_user? ? Faq.all : Faq.active - @faqs = faqs.search_by_params(params.to_unsafe_h.slice("query", "inactive")) + faqs = current_user&.super_user? ? Faq.all : (current_user ? Faq.published : Faq.publicly_visible) + @faqs = faqs.search_by_params(params.to_unsafe_h.slice("query", "published")) .by_position .page(params[:page]) end @@ -59,6 +60,6 @@ def set_faq # Strong parameters def faq_params - params.require(:faq).permit(:question, :answer, :inactive, :position, :publicly_visible) + params.require(:faq).permit(:question, :answer, :position, :published, :publicly_visible) end end diff --git a/app/controllers/monthly_reports_controller.rb b/app/controllers/monthly_reports_controller.rb index 1aff7c7e2..b82f96199 100644 --- a/app/controllers/monthly_reports_controller.rb +++ b/app/controllers/monthly_reports_controller.rb @@ -122,7 +122,7 @@ def load_agencies def render_form @workshop_list = Workshop.created_by_id(current_user.id) - .where(inactive: false, windows_type: 3) + .where(published: true, windows_type: 3) .order(title: :asc) build_month_and_year diff --git a/app/controllers/quotes_controller.rb b/app/controllers/quotes_controller.rb index ee871fa0b..674910805 100644 --- a/app/controllers/quotes_controller.rb +++ b/app/controllers/quotes_controller.rb @@ -66,7 +66,7 @@ def quote_params params.require(:quote).permit( :age, :gender, - :inactive, + :published, :quote, :speaker_name, :workshop_id, diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 0e75dac83..326810d60 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -56,7 +56,7 @@ def edit def edit_story @workshop_list = Workshop.created_by_id(current_user.id) - .where(inactive: false, windows_type: 3) + .where(published: true, windows_type: 3) .order(title: :asc) @report = Report.find(params[:id]) diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index c6b740f03..0b50fb88a 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -1,5 +1,6 @@ class ResourcesController < ApplicationController include ExternallyRedirectable, AssetUpdatable, AhoyTracking + skip_before_action :authenticate_user!, only: [ :index, :show ] def index authorize! @@ -139,7 +140,7 @@ def resource_id_param def resource_params params.require(:resource).permit( - :rhino_text, :kind, :male, :female, :title, :featured, :inactive, :publicly_visible, :publicly_featured, :url, + :rhino_text, :kind, :male, :female, :title, :featured, :published, :publicly_visible, :publicly_featured, :url, :agency, :author, :filemaker_code, :windows_type_id, :position, categorizable_items_attributes: [ :id, :category_id, :_destroy ], category_ids: [], sectorable_items_attributes: [ :id, :sector_id, :is_leader, :_destroy ], sector_ids: [] diff --git a/app/controllers/sectors_controller.rb b/app/controllers/sectors_controller.rb index cf8bb765a..a0d96e8cc 100644 --- a/app/controllers/sectors_controller.rb +++ b/app/controllers/sectors_controller.rb @@ -5,8 +5,8 @@ def index per_page = params[:number_of_items_per_page].presence || 25 unfiltered = Sector.all filtered = unfiltered.sector_name(params[:sector_name]) - .published_search(params[:published_search]) - .order(:name) + .published(params[:published]) + .order(:name) @sectors = filtered.paginate(page: params[:page], per_page: per_page) @count_display = if filtered.count == unfiltered.count diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index f03702ab2..f57da3d13 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -1,5 +1,6 @@ class StoriesController < ApplicationController include ExternallyRedirectable, AssetUpdatable, AhoyTracking + skip_before_action :authenticate_user!, only: [ :index, :show ] before_action :set_story, only: [ :show, :edit, :update, :destroy ] def index diff --git a/app/controllers/taggings_controller.rb b/app/controllers/taggings_controller.rb index c5c192d79..dbe21d2a8 100644 --- a/app/controllers/taggings_controller.rb +++ b/app/controllers/taggings_controller.rb @@ -2,8 +2,8 @@ class TaggingsController < ApplicationController include AhoyTracking def index - @sector_names = params[:sector_names].to_s - @category_names = params[:category_names].to_s + @sector_names = params[:sector_names] + @category_names = params[:category_names] number_of_items_per_page = params[:number_of_items_per_page].present? ? params[:number_of_items_per_page].to_i : 9 pages = { diff --git a/app/controllers/tutorials_controller.rb b/app/controllers/tutorials_controller.rb index 8ee78389a..8aa999bac 100644 --- a/app/controllers/tutorials_controller.rb +++ b/app/controllers/tutorials_controller.rb @@ -1,10 +1,11 @@ class TutorialsController < ApplicationController include AhoyTracking + skip_before_action :authenticate_user!, only: [ :index, :show ] before_action :set_tutorial, only: [ :show, :edit, :update, :destroy ] def index per_page = params[:number_of_items_per_page].presence || 25 - unfiltered = current_user.super_user? ? Tutorial.all : Tutorial.published + unfiltered = current_user&.super_user? ? Tutorial.all : current_user ? Tutorial.published : Tutorial.publicly_visible filtered = unfiltered.search_by_params(params) @count_display = filtered.count == unfiltered.count ? unfiltered.count : "#{filtered.count}/#{unfiltered.count}" @tutorials = filtered.order(:position).paginate(page: params[:page], per_page: per_page).decorate diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0419fbbac..f243f5ee7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -154,8 +154,8 @@ def set_facilitator def set_form_variables set_facilitator @user.project_users.first || @user.project_users.build - projects = if current_user.super_user? - Project.active + projects = if current_user.admin? + Project.published else current_user.projects end @@ -177,7 +177,7 @@ def user_params :phone, :phone2, :phone3, :birthday, :best_time_to_call, :comment, :notes, :primary_address, :avatar, :subscribecode, :agency_id, :facilitator_id, :created_by_id, :updated_by_id, - :confirmed, :inactive, :super_user, :legacy, :legacy_id, + :confirmed, :published, :super_user, :legacy, :legacy_id, project_users_attributes: [ :id, :project_id, :position, :title, :inactive, :_destroy ] ) end diff --git a/app/controllers/workshop_logs_controller.rb b/app/controllers/workshop_logs_controller.rb index 888ac75d2..28890f1b8 100644 --- a/app/controllers/workshop_logs_controller.rb +++ b/app/controllers/workshop_logs_controller.rb @@ -126,7 +126,7 @@ def set_index_variables # needs to not be private .order(:last_name, :first_name) @projects = if current_user.super_user? # Project.where(id: @workshop_logs_unpaginated.pluck(:project_id)).order(:name) - Project.active.order(:name) + Project.published.order(:name) else current_user.projects.order(:name) end @@ -176,7 +176,7 @@ def set_form_variables form = FormBuilder.where(windows_type_id: @windows_type_id) .first&.forms.first # because there's only one form per form_builder if form - @report_field_answers = form.form_fields.active.order(:position).map do |field| + @report_field_answers = form.form_fields.published.order(:position).map do |field| @workshop_log.report_form_field_answers.find_or_initialize_by(form_field: field) end end diff --git a/app/controllers/workshop_variations_controller.rb b/app/controllers/workshop_variations_controller.rb index 40d7ba805..e61f8a4a4 100644 --- a/app/controllers/workshop_variations_controller.rb +++ b/app/controllers/workshop_variations_controller.rb @@ -10,7 +10,7 @@ def index WorkshopVariation .includes(:workshop) .joins(:workshop) - .where(workshops: { inactive: false }) + .where(workshops: { published: true }) .order("workshop_variations.created_at DESC, workshops.title, workshop_variations.name") .paginate(page: params[:page], per_page: 25) .decorate @@ -87,7 +87,7 @@ def set_form_variables def workshop_variation_params params.require(:workshop_variation).permit( - [ :name, :code, :inactive, :position, + [ :name, :code, :published, :position, :youtube_url, :created_by_id, :workshop_id ] ) diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 22966f2e6..6f1f52701 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -1,12 +1,14 @@ class WorkshopsController < ApplicationController include AssetUpdatable, AhoyTracking + skip_before_action :authenticate_user!, only: [ :index, :show ] + def index @category_types = CategoryType.published.order(:name).decorate @sectors = Sector.published @windows_types = WindowsType.all if turbo_frame_request? - search_service = WorkshopSearchService.new(params, super_user: current_user.super_user?).call + search_service = WorkshopSearchService.new(params, user: current_user).call @sort = search_service.sort track_index_intent(Workshop, search_service.workshops, params) @@ -189,9 +191,9 @@ def search def set_show @quotes = Quote.where(workshop_id: @workshop.id).active - @leader_spotlights = @workshop.associated_resources.leader_spotlights.where(inactive: false) + @leader_spotlights = @workshop.associated_resources.leader_spotlights.where(published: true) @workshop_variations = @workshop.workshop_variations.active - @sectors = @workshop.sectorable_items.published.map { |item| item.sector if item.sector.published }.compact if @workshop.sectorable_items.any? + @sectors = @workshop.sectorable_items.published.map { |item| item.sector if item.sector.published? }.compact if @workshop.sectorable_items.any? end @@ -239,7 +241,7 @@ def view_all_workshops? def workshop_params params.require(:workshop).permit( - :title, :featured, :inactive, + :title, :featured, :published, :full_name, :user_id, :windows_type_id, :workshop_idea_id, :month, :year, :publicly_visible, diff --git a/app/decorators/community_news_decorator.rb b/app/decorators/community_news_decorator.rb index 4cdc4755d..2d34d1e31 100644 --- a/app/decorators/community_news_decorator.rb +++ b/app/decorators/community_news_decorator.rb @@ -8,8 +8,4 @@ def detail(length: nil) def external_url object.reference_url end - - def inactive? - !published? - end end diff --git a/app/decorators/facilitator_decorator.rb b/app/decorators/facilitator_decorator.rb index c0fb138b6..98e5bcb80 100644 --- a/app/decorators/facilitator_decorator.rb +++ b/app/decorators/facilitator_decorator.rb @@ -12,10 +12,6 @@ def default_display_image "missing.png" end - def inactive? - !user ? false : user&.inactive? - end - def primary_asset avatar end diff --git a/app/decorators/story_decorator.rb b/app/decorators/story_decorator.rb index d8db279e1..d89f6ed1d 100644 --- a/app/decorators/story_decorator.rb +++ b/app/decorators/story_decorator.rb @@ -9,10 +9,6 @@ def external_url object.website_url end - def inactive? - !published? - end - def workshop_title workshop&.title || external_workshop_title end diff --git a/app/helpers/admin_cards_helper.rb b/app/helpers/admin_cards_helper.rb index aa91949e0..fcd2035cd 100644 --- a/app/helpers/admin_cards_helper.rb +++ b/app/helpers/admin_cards_helper.rb @@ -45,11 +45,11 @@ def reference_cards [ model_card(:categories, icon: "🗂️", intensity: 100, - params: { published_search: true }), + params: { published: true }), model_card(:sectors, icon: "🏭", intensity: 100, title: "Service populations", - params: { published_search: true }), + params: { published: true }), custom_card("Project statuses", project_statuses_path, icon: "🧮", color: :emerald, intensity: 100), custom_card("Windows types", windows_types_path, icon: "🪟") ] diff --git a/app/helpers/analytics_helper.rb b/app/helpers/analytics_helper.rb index cd6da7d2e..47b4e8216 100644 --- a/app/helpers/analytics_helper.rb +++ b/app/helpers/analytics_helper.rb @@ -6,7 +6,7 @@ def print_button(record, icon_class: "fas fa-print", button_text: nil, title: "Print", - image_toggle: false) + image_toggle: false) model = record.respond_to?(:object) ? record.object : record resolved_type = diff --git a/app/helpers/tag_matrix_helper.rb b/app/helpers/tag_matrix_helper.rb index 8915e87fc..592b3243d 100644 --- a/app/helpers/tag_matrix_helper.rb +++ b/app/helpers/tag_matrix_helper.rb @@ -8,7 +8,7 @@ def tag_count_for(model, tag:, type:) model.category_names(tag.name) end - scope.published.count + scope.published.unscope(:group).count end def tag_link_for(model, tag:, type:) diff --git a/app/helpers/title_display_helper.rb b/app/helpers/title_display_helper.rb index 5b035dd2a..6064a13e3 100644 --- a/app/helpers/title_display_helper.rb +++ b/app/helpers/title_display_helper.rb @@ -4,12 +4,10 @@ def title_with_badges(record, font_size: "text-lg", record_title: nil, fragments = [] # --- Hidden badge --- - if show_hidden_badge && controller_name != "dashboard" && ( - record.respond_to?(:inactive?) && record.inactive? && controller_name != "dashboard" || - record.respond_to?(:published?) && !record.published?) + if show_hidden_badge && controller_name != "dashboard" && record.respond_to?(:published?) && !record.published? fragments << content_tag( :span, - content_tag(:i, "", class: "fa-solid fa-eye-slash mr-1") + " Hidden", + content_tag(:i, "", class: "fa-solid fa-eye-slash mr-1") + " Unpublished", class: "inline-flex items-center px-2 py-0.5 rounded-full text-sm font-medium bg-blue-100 text-gray-600 whitespace-nowrap" ) diff --git a/app/models/category.rb b/app/models/category.rb index 8bd02af14..cdc43137e 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,6 +1,5 @@ class Category < ApplicationRecord - include NameFilterable - include Positioning + include NameFilterable, Publishable positioned on: :category_type_id @@ -8,8 +7,9 @@ class Category < ApplicationRecord has_many :categorizable_items, dependent: :destroy has_many :workshops, through: :categorizable_items, source: :categorizable, source_type: "Workshop" + # Scopes + # See NameFilterable, Publishable scope :age_ranges, -> { joins(:category_type).where(category_types: { name: "AgeRange" }) } - scope :published, -> { where(published: true) } scope :ordered_by_position_and_name, -> { reorder(position: :asc, name: :asc) } # Validations @@ -29,9 +29,6 @@ class Category < ApplicationRecord category_type_id.present? ? where(category_type_id: category_type_id) : all } scope :category_name, ->(category_name) { category_name.present? ? where("categories.name LIKE ?", "%#{category_name}%") : all } - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) } - scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all } private diff --git a/app/models/category_type.rb b/app/models/category_type.rb index 66381712b..4a8abd1c9 100644 --- a/app/models/category_type.rb +++ b/app/models/category_type.rb @@ -1,9 +1,12 @@ class CategoryType < ApplicationRecord + include Publishable + has_many :categories, class_name: "Category", foreign_key: :category_type_id, dependent: :destroy has_many :categorizable_items, through: :categories, dependent: :destroy # Validations validates :name, presence: true, uniqueness: { case_sensitive: false } - scope :published, -> { where(published: true) } + # Scopes + # See Publishable end diff --git a/app/models/community_news.rb b/app/models/community_news.rb index ca0ac2275..cc5add45e 100644 --- a/app/models/community_news.rb +++ b/app/models/community_news.rb @@ -1,5 +1,5 @@ class CommunityNews < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable + include Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable belongs_to :project, optional: true belongs_to :windows_type, optional: true @@ -33,31 +33,22 @@ class CommunityNews < ApplicationRecord accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :gallery_assets, allow_destroy: true, reject_if: :all_blank + # Scopes + # See Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable + # SearchCop include SearchCop search_scope :search do attributes :title, :published, facilitator_first: "facilitators.first_name", facilitator_last: "facilitators.last_name" - scope { join_rich_texts.left_joins(author: :facilitator) } attributes action_text_body: "action_text_rich_texts.plain_text_body" end - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } - scope :community_news_name, ->(community_news_name) { - community_news_name.present? ? where("community_news.name LIKE ?", "%#{community_news_name}%") : all } - scope :featured, -> { where(featured: true) } - scope :publicly_featured, -> { published.where(publicly_featured: true) } - scope :publicly_visible, -> { published.where(publicly_visible: true) } - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) } - scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all } - def self.search_by_params(params) conditions = {} conditions[:title] = params[:title] if params[:title].present? conditions[:query] = params[:query] if params[:query].present? - conditions[:published] = params[:published_search] if params[:published_search].present? + conditions[:published] = params[:published] if params[:published].present? self.search(conditions) end diff --git a/app/models/concerns/featureable.rb b/app/models/concerns/featureable.rb index d18ad57db..b52b1d157 100644 --- a/app/models/concerns/featureable.rb +++ b/app/models/concerns/featureable.rb @@ -3,6 +3,7 @@ module Featureable included do scope :featured, -> { published.where(featured: true) } - scope :publicly_featured, -> { published.where(publicly_featured: true, publicly_visible: true) } + scope :publicly_featured, -> { published.publicly_visible.where(publicly_featured: true) } + scope :featured_or_publicly_featured, -> { featured.or(publicly_featured) } end end diff --git a/app/models/concerns/name_filterable.rb b/app/models/concerns/name_filterable.rb index 8b819e648..cfea99629 100644 --- a/app/models/concerns/name_filterable.rb +++ b/app/models/concerns/name_filterable.rb @@ -3,7 +3,8 @@ module NameFilterable class_methods do def names(input) - return none if input.blank? + # Param not provided → do not filter + return all if input.nil? parsed = Array(input) @@ -12,6 +13,7 @@ def names(input) .reject(&:blank?) .map(&:downcase) + # Param provided but empty → intentionally no matches return none if parsed.empty? conditions = parsed.map { "LOWER(name) LIKE ?" }.join(" OR ") diff --git a/app/models/concerns/publishable.rb b/app/models/concerns/publishable.rb new file mode 100644 index 000000000..dbe809865 --- /dev/null +++ b/app/models/concerns/publishable.rb @@ -0,0 +1,14 @@ +module Publishable + extend ActiveSupport::Concern + + included do + scope :published, ->(flag = nil) do + value = flag.nil? || flag == "" ? true : ActiveModel::Type::Boolean.new.cast(flag) + where(published: value) + end + + if table_exists? && column_names.include?("publicly_visible") + scope :publicly_visible, -> { published.where(publicly_visible: true) } + end + end +end diff --git a/app/models/concerns/tag_filterable.rb b/app/models/concerns/tag_filterable.rb index 6ea042c28..df852f9f0 100644 --- a/app/models/concerns/tag_filterable.rb +++ b/app/models/concerns/tag_filterable.rb @@ -1,10 +1,14 @@ -# app/models/concerns/tag_filterable.rb module TagFilterable extend ActiveSupport::Concern + included do + scope :category_names, ->(names) { names.nil? ? all : tag_names(:categories, names) } + scope :sector_names, ->(names) { names.nil? ? all : tag_names(:sectors, names) } + end + class_methods do - def tag_names(association, names) - return all if names.blank? + def tag_names(association, names, match: :all) + return none if names.blank? # param present but empty parsed_names = Array(names) @@ -13,14 +17,21 @@ def tag_names(association, names) .reject(&:blank?) .map(&:downcase) - return all if parsed_names.empty? + return none if parsed_names.empty? + + reflection = reflect_on_association(association) + table_name = reflection.klass.table_name + parent_table = self.table_name - reflection = reflect_on_association(association) - table_name = reflection.klass.table_name + relation = + joins(association) + .where("LOWER(#{table_name}.name) IN (?)", parsed_names) - joins(association) - .where("LOWER(#{table_name}.name) IN (?)", parsed_names) - .distinct + match == :all ? + relation.group("#{parent_table}.id") + .having("COUNT(DISTINCT #{table_name}.id) = ?", parsed_names.size) + : + relation.distinct end end end diff --git a/app/models/event.rb b/app/models/event.rb index fcbd38cfc..cafc6b0e4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,5 +1,5 @@ class Event < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable + include Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable belongs_to :created_by, class_name: "User", optional: true has_many :bookmarks, as: :bookmarkable, dependent: :destroy @@ -20,7 +20,7 @@ class Event < ApplicationRecord # Validations validates_presence_of :title, :start_date, :end_date - validates_inclusion_of :inactive, in: [ true, false ] + validates_inclusion_of :published, in: [ true, false ] validates_numericality_of :cost_cents, greater_than_or_equal_to: 0, allow_nil: true # Nested attributes @@ -33,25 +33,11 @@ class Event < ApplicationRecord attributes :title, :description end - - # Action Policy - scope :featured, -> { - where(featured: true, inactive: false) - .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) - } - scope :publicly_featured, -> { - where(publicly_visible: true, publicly_featured: true, inactive: false) - .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) - } - scope :publicly_visible, -> { published.where(publicly_visible: true) - .where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) - } - - scope :published, ->(published = nil) { published.to_s.present? ? - where(inactive: !published) : where(inactive: false) } - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } - + # Scopes + # See Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable + scope :featured, -> { registerable.where(published: true, featured: true) } + scope :publicly_featured, -> { registerable.where(published: true, publicly_visible: true, publicly_featured: true) } + scope :registerable, -> { where("registration_close_date IS NULL OR registration_close_date >= ?", Time.current) } def self.search_by_params(params) stories = self.all @@ -62,13 +48,8 @@ def self.search_by_params(params) stories end - def published? - !inactive - end - def registerable? - !inactive && - (registration_close_date.nil? || registration_close_date >= Time.current) + published && (registration_close_date.nil? || registration_close_date >= Time.current) end def full_name diff --git a/app/models/facilitator.rb b/app/models/facilitator.rb index 51e4834ea..192aadba2 100644 --- a/app/models/facilitator.rb +++ b/app/models/facilitator.rb @@ -1,5 +1,5 @@ class Facilitator < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable + include Publishable, TagFilterable, Trendable, WindowsTypeFilterable belongs_to :created_by, class_name: "User" belongs_to :updated_by, class_name: "User" @@ -53,17 +53,13 @@ class Facilitator < ApplicationRecord attributes contact_methods_phone: "contact_methods.value" end - scope :active, -> { all } # TODO - implement inactive field - scope :published, -> { active.searchable } - scope :published, ->(published = nil) { published ? active.searchable(published) : active.searchable } + scope :published, -> { where(published: true).searchable } scope :searchable, ->(searchable = nil) { searchable ? where(profile_is_searchable: searchable) : where(profile_is_searchable: true) } scope :project_name, ->(project_name) { return all if project_name.blank? left_joins(user: { project_users: :project }) .where("projects.name LIKE ?", "%#{sanitize_sql_like(project_name)}%") .distinct } - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } def self.search_by_params(params) results = self.all diff --git a/app/models/faq.rb b/app/models/faq.rb index 9ec9973cc..115b66ed2 100644 --- a/app/models/faq.rb +++ b/app/models/faq.rb @@ -1,16 +1,13 @@ class Faq < ApplicationRecord + include Publishable positioned # Validations validates_presence_of :question, :answer # Scopes - scope :active, -> { where(inactive: false) } + # See Publishable scope :by_position, -> { order(position: :asc) } - scope :publicly_visible, -> { published.where(publicly_visible: true) } - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) - } # Search Cop include SearchCop @@ -21,9 +18,9 @@ class Faq < ApplicationRecord def self.search_by_params(params) results = self.all results = results.search(params[:query]) if params[:query].present? - if params[:inactive].to_s.present? - value = ActiveModel::Type::Boolean.new.cast(params[:inactive]) - results = results.where(inactive: value) + if params[:published].to_s.present? + value = ActiveModel::Type::Boolean.new.cast(params[:published]) + results = results.where(published: value) end results end diff --git a/app/models/form_field.rb b/app/models/form_field.rb index 4199d24da..412b87586 100644 --- a/app/models/form_field.rb +++ b/app/models/form_field.rb @@ -35,7 +35,7 @@ class FormField < ApplicationRecord accepts_nested_attributes_for :form_field_answer_options default_scope { order(position: :desc) } - scope :active, -> { where(status: "active") } + scope :published, -> { where(status: "active") } # Methods def name diff --git a/app/models/notification.rb b/app/models/notification.rb index 2c3dcbcdb..09ca2adc3 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -23,6 +23,7 @@ class Notification < ApplicationRecord facilitator ].freeze + # Scopes scope :delivered, -> { where.not(delivered_at: nil) } scope :undelivered, -> { where(delivered_at: nil) } diff --git a/app/models/project.rb b/app/models/project.rb index febe5528c..ec75343b7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,5 +1,5 @@ class Project < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable + include Publishable, TagFilterable, Trendable, WindowsTypeFilterable belongs_to :project_status belongs_to :project_obligation, optional: true @@ -39,6 +39,8 @@ class Project < ApplicationRecord attributes :name end + # Scopes + # See Publishable, TagFilterable, Trendable, WindowsTypeFilterable scope :address, ->(address) do return all if address.blank? exact = address.to_s @@ -59,11 +61,7 @@ class Project < ApplicationRecord SQL wildcard: wildcard, exact: exact) end - scope :active, ->(active = nil) { active ? where(inactive: !active) : where(inactive: false) } scope :project_ids, ->(project_ids) { where(id: project_ids.to_s.split("-").map(&:to_i)) } - scope :published, ->(published = nil) { published ? active(published) : active } - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } def self.search_by_params(params) projects = self.all diff --git a/app/models/quote.rb b/app/models/quote.rb index 45f74171e..a302805b8 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -1,5 +1,5 @@ class Quote < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable + include Publishable, TagFilterable, Trendable, WindowsTypeFilterable belongs_to :workshop, optional: true has_many :bookmarks, as: :bookmarkable, dependent: :destroy @@ -24,8 +24,6 @@ class Quote < ApplicationRecord attributes :quote end - scope :active, ->(active = nil) { active ? where(inactive: !active) : where(inactive: false) } - scope :published, ->(published = nil) { published ? active(published) : active } scope :category_names, ->(names) { tag_names(:categories, names) } scope :sector_names, ->(names) { tag_names(:sectors, names) } diff --git a/app/models/resource.rb b/app/models/resource.rb index 1ea6f0c82..da89e66df 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -1,5 +1,5 @@ class Resource < ApplicationRecord - include Featureable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable + include Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable include Rails.application.routes.url_helpers include ActionText::Attachable @@ -44,7 +44,7 @@ class Resource < ApplicationRecord through: :action_text_mentions # Default values - attribute :inactive, :boolean, default: false + attribute :published, :boolean, default: false # Validations validates :title, presence: true, uniqueness: { case_sensitive: false } @@ -74,26 +74,15 @@ class Resource < ApplicationRecord # Scopes scope :by_created, -> { order(created_at: :desc) } scope :by_featured_first, -> { order(featured: :desc, created_at: :desc) } - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } scope :kinds, ->(kinds) { kinds = Array(kinds).flatten.map(&:to_s) - where(kind: kinds) - } + where(kind: kinds) } scope :leader_spotlights, -> { kinds("LeaderSpotlight") } - scope :publicly_featured, -> { published.where(publicly_featured: true) } - scope :publicly_visible, -> { published.where(publicly_visible: true) } scope :published_kinds, -> { where(kind: PUBLISHED_KINDS) } - scope :published, ->(published = nil) { - if [ "true", "false" ].include?(published) - result = where(inactive: published == "true" ? false : true) - else - result = where(inactive: false) - end - result.published_kinds - } - scope :published_search, ->(published_search = nil) { published_search.present? ? published(published_search) : published_kinds } - + scope :published, ->(flag = nil) do + value = flag.nil? || flag == "" ? true : ActiveModel::Type::Boolean.new.cast(flag) + result = value ? published_kinds.where(published: true) : where(published: false) + end scope :recent, -> { published.by_created } scope :sector_impact, -> { where(kind: "SectorImpact") } scope :scholarship, -> { where(kind: "Scholarship") } @@ -109,7 +98,7 @@ def self.search_by_params(params) resources = resources.windows_type_name(params[:windows_type_name]) if params[:windows_type_name].present? resources = resources.title(params[:title]) if params[:title].present? resources = resources.kinds(params[:kinds]) if params[:kinds].present? - resources = resources.published_search(params[:published_search]) if params[:published_search].present? + resources = resources.published(params[:published]) if params[:published].present? resources end @@ -144,8 +133,8 @@ def month created_at.month end - def published? - !inactive? && PUBLISHED_KINDS.include?(kind) + def published? # AR override + self[:published] && PUBLISHED_KINDS.include?(kind) end ## ActionText:Attachable diff --git a/app/models/sector.rb b/app/models/sector.rb index a5fd9982e..bb794ff5e 100644 --- a/app/models/sector.rb +++ b/app/models/sector.rb @@ -1,5 +1,5 @@ class Sector < ApplicationRecord - include NameFilterable + include NameFilterable, Publishable SECTOR_TYPES = [ "Veterans & Military", "Sexual Assault", "Substance Abuse", "LGBTQIA", "Child Abuse", "Education/Schools", "Domestic Violence", "Other" ] @@ -17,9 +17,6 @@ class Sector < ApplicationRecord after_destroy :expire_sectors_cache # Scopes - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) } - scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all } scope :sector_name, ->(sector_name) { sector_name.present? ? where("sectors.name LIKE ?", "%#{sector_name}%") : all } scope :has_taggings, -> { joins(:sectorable_items).distinct } diff --git a/app/models/sectorable_item.rb b/app/models/sectorable_item.rb index 5953c49e4..64614017b 100644 --- a/app/models/sectorable_item.rb +++ b/app/models/sectorable_item.rb @@ -7,7 +7,7 @@ class SectorableItem < ApplicationRecord validates_presence_of :sectorable_type, :sectorable_id, :sector_id validates :sector_id, uniqueness: { scope: [ :sectorable_type, :sectorable_id ] } - scope :published, -> { where(inactive: false) } + scope :published, -> { where(published: true) } # Methods def title diff --git a/app/models/story.rb b/app/models/story.rb index 5b91ebda0..d31d96314 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -1,5 +1,5 @@ class Story < ApplicationRecord - include TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable + include Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable belongs_to :created_by, class_name: "User" belongs_to :updated_by, class_name: "User" @@ -49,23 +49,13 @@ class Story < ApplicationRecord end # Scopes - scope :featured, -> { where(featured: true) } - scope :publicly_featured, -> { where(publicly_featured: true) } - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } - scope :story_name, ->(story_name) { - story_name.present? ? where("stories.name LIKE ?", "%#{story_name}%") : all } - scope :publicly_featured, -> { published.where(publicly_featured: true) } - scope :publicly_visible, -> { published.where(publicly_visible: true) } - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) } - scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all } + # See Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable def self.search_by_params(params) conditions = {} conditions[:title] = params[:title] if params[:title].present? conditions[:query] = params[:query] if params[:query].present? - conditions[:published] = params[:published_search] if params[:published_search].present? + conditions[:published] = params[:published] if params[:published].present? self.search(conditions) end diff --git a/app/models/tutorial.rb b/app/models/tutorial.rb index b967ee1e0..03ce2c966 100644 --- a/app/models/tutorial.rb +++ b/app/models/tutorial.rb @@ -1,5 +1,5 @@ class Tutorial < ApplicationRecord - include TagFilterable, Trendable, RichTextSearchable + include Publishable, TagFilterable, Trendable, RichTextSearchable has_rich_text :rhino_body @@ -28,11 +28,6 @@ class Tutorial < ApplicationRecord end scope :body, ->(body) { where("body like ?", "%#{ body }%") } - scope :publicly_visible, -> { published.where(publicly_visible: true) } - scope :published, ->(published = nil) { - [ "true", "false" ].include?(published) ? where(published: published) : where(published: true) - } - scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all } scope :title, ->(title) { where("title like ?", "%#{ title }%") } scope :tutorial_name, ->(tutorial_name) { title(tutorial_name) } diff --git a/app/models/workshop.rb b/app/models/workshop.rb index d80a10053..9b166f66a 100644 --- a/app/models/workshop.rb +++ b/app/models/workshop.rb @@ -1,5 +1,5 @@ class Workshop < ApplicationRecord - include Featureable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable + include Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable include Rails.application.routes.url_helpers include ActionText::Attachable include ActiveModel::Dirty @@ -115,16 +115,10 @@ class Workshop < ApplicationRecord # Scopes - scope :category_names, ->(names) { tag_names(:categories, names) } - scope :sector_names, ->(names) { tag_names(:sectors, names) } + # See Featureable, Publishable, TagFilterable, Trendable, WindowsTypeFilterable, RichTextSearchable scope :created_by_id, ->(created_by_id) { where(user_id: created_by_id) } scope :legacy, -> { where(legacy: true) } - scope :publicly_featured, -> { published.where(publicly_featured: true) } - scope :publicly_visible, -> { published.where(publicly_visible: true) } - scope :published, ->(published = nil) { published.to_s.present? ? - where(inactive: !published) : where(inactive: false) } scope :title, ->(title) { where("workshops.title like ?", "%#{ title }%") } - scope :windows_type_ids, ->(windows_type_ids) { where(windows_type_id: windows_type_ids) } scope :order_by_date, ->(sort_order = "asc") { order(Arel.sql(<<~SQL.squish)) COALESCE( @@ -136,14 +130,13 @@ class Workshop < ApplicationRecord ) #{sort_order == "asc" ? "ASC" : "DESC"} SQL } + scope :title, ->(title) { where("workshops.title like ?", "%#{ title }%") } + scope :windows_type_ids, ->(windows_type_ids) { where(windows_type_id: windows_type_ids) } scope :with_bookmarks_count, -> { left_joins(:bookmarks) .select("workshops.*, COUNT(bookmarks.id) AS bookmarks_count") .group("workshops.id") } - scope :featured_or_publicly_featured, -> { - where("(featured = ? OR publicly_featured = ?) AND inactive = ?", true, true, false) - } # Search Cop include SearchCop @@ -288,7 +281,7 @@ def invalidate_featured_cache_if_changed end def featured_or_publicly_featured_changed? - featured_changed? || publicly_featured_changed? || inactive_changed? + featured_changed? || publicly_featured_changed? || published_changed? end def attach_assets_from_idea! diff --git a/app/models/workshop_variation.rb b/app/models/workshop_variation.rb index 4f3ab403d..276b1050a 100644 --- a/app/models/workshop_variation.rb +++ b/app/models/workshop_variation.rb @@ -1,5 +1,5 @@ class WorkshopVariation < ApplicationRecord - include Trendable + include Trendable, Publishable belongs_to :workshop belongs_to :created_by, class_name: "User", optional: true @@ -20,9 +20,6 @@ class WorkshopVariation < ApplicationRecord accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :gallery_assets, allow_destroy: true, reject_if: :all_blank - scope :active, -> { where(inactive: false) } - scope :published, -> { all } - delegate :windows_type, to: :workshop def description diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index 68a1edafc..23e5b6768 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -8,13 +8,12 @@ class ApplicationPolicy < ActionPolicy::Base # Read more about authorization context: https://actionpolicy.evilmartians.io/#/authorization_context # authorize :user, optional: true, allow_nil: true - pre_check :verify_authenticated! default_rule :manage? alias_rule :index?, :show?, :new?, :create?, :edit?, :update?, :destroy?, to: :manage? def manage? - admin? + authenticated? && admin? end private diff --git a/app/policies/dashboard_policy.rb b/app/policies/dashboard_policy.rb index 6915a22dc..ec4f84237 100644 --- a/app/policies/dashboard_policy.rb +++ b/app/policies/dashboard_policy.rb @@ -1,6 +1,4 @@ class DashboardPolicy < ApplicationPolicy - skip_pre_check :verify_authenticated! - def index? true end diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb index 2891d5e0a..37e4706d1 100644 --- a/app/policies/event_policy.rb +++ b/app/policies/event_policy.rb @@ -3,8 +3,6 @@ class EventPolicy < ApplicationPolicy # # override or add new rules here that are not defined in ApplicationPolicy - skip_pre_check :verify_authenticated! - def index? true end diff --git a/app/services/workshop_from_idea_service.rb b/app/services/workshop_from_idea_service.rb index e78b0597a..30f5515cf 100644 --- a/app/services/workshop_from_idea_service.rb +++ b/app/services/workshop_from_idea_service.rb @@ -61,7 +61,7 @@ def attributes_from_idea month: workshop_idea.created_at.month, year: workshop_idea.created_at.year, featured: false, - inactive: true + published: false ) end diff --git a/app/services/workshop_search_service.rb b/app/services/workshop_search_service.rb index bc59590e2..ee0e547eb 100644 --- a/app/services/workshop_search_service.rb +++ b/app/services/workshop_search_service.rb @@ -1,10 +1,11 @@ class WorkshopSearchService - attr_reader :params, :super_user + attr_reader :params, :user, :super_user attr_accessor :workshops, :sort - def initialize(params = {}, super_user: false) + def initialize(params = {}, user: nil) @params = params - @super_user = super_user + @user = user + @super_user = user&.super_user? @sort = default_sort @workshops = if @sort == "popularity" @@ -16,7 +17,6 @@ def initialize(params = {}, super_user: false) # Main entry point def call - normalize_published_param filter_by_params order_by_params resolve_ids_order @@ -59,19 +59,23 @@ def filter_by_windows_type_name def filter_by_published_status if super_user - active = ActiveModel::Type::Boolean.new.cast(params[:active]) if params.key?(:active) - inactive = ActiveModel::Type::Boolean.new.cast(params[:inactive]) if params.key?(:inactive) - - @workshops = - if active && !inactive - @workshops.published(true) - elsif inactive && !active - @workshops.published(false) - else - @workshops - end - else + pub = params.key?(:published) ? ActiveModel::Type::Boolean.new.cast(params[:published]) : nil + unpub = params.key?(:unpublished) ? ActiveModel::Type::Boolean.new.cast(params[:unpublished]) : nil + + case [ pub, unpub ] + when [ true, nil ], [ true, false ] + @workshops = @workshops.published(true) # ONLY published + when [ nil, true ], [ false, true ] + @workshops = @workshops.published(false) # ONLY unpublished + when [ false, false ] + @workshops = @workshops.none # NONE + else # incl [ nil, nil ] && [ true, true ] + @workshops # ALL + end + elsif user @workshops = @workshops.published + else + @workshops = @workshops.publicly_visible end end @@ -183,20 +187,6 @@ def sector_ids_from_names .pluck(:id) end - def normalize_published_param - return unless params.key?(:published) - - published = ActiveModel::Type::Boolean.new.cast(params[:published]) - - if published - params[:active] = true - params.delete(:inactive) - else - params[:inactive] = true - params.delete(:active) - end - end - # --- Sorting --- def order_by_params diff --git a/app/views/admin/analytics/index.html.erb b/app/views/admin/analytics/index.html.erb index 67a2638a1..52af3eb98 100644 --- a/app/views/admin/analytics/index.html.erb +++ b/app/views/admin/analytics/index.html.erb @@ -108,16 +108,32 @@ -
-
+
+

Community News

+
+ <%= render "admin/analytics/popular_list", - title: "Community news", + title: "Most viewed", records: @most_viewed_community_news, path_method: :community_news_path, metric: :view_count, color: :community_news %> + <%= render "admin/analytics/popular_list", + title: "Most printed", + records: @most_printed_community_news, + path_method: :community_news_path, + metric: :print_count, + color: :community_news + %> +
+
+ +
+

Stories

+
+ <%= render "admin/analytics/popular_list", title: "Stories", records: @most_viewed_stories, @@ -126,7 +142,18 @@ color: :stories %> + <%= render "admin/analytics/popular_list", + title: "Most printed", + records: @most_printed_stories, + path_method: :story_path, + metric: :print_count, + color: :stories + %> +
+
+
+
<%= render "admin/analytics/popular_list", title: "Events", records: @most_viewed_events, diff --git a/app/views/banners/_form.html.erb b/app/views/banners/_form.html.erb index 58abe7220..310ff82b2 100644 --- a/app/views/banners/_form.html.erb +++ b/app/views/banners/_form.html.erb @@ -18,10 +18,8 @@ }, hint: "You can include limited HTML (e.g. , , )." %> - - <%= f.input :show, + <%= f.input :published, as: :boolean, - label: "Published?", input_html: { class: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" }, diff --git a/app/views/banners/edit.html.erb b/app/views/banners/edit.html.erb index 9866ed1c3..c722c9aea 100644 --- a/app/views/banners/edit.html.erb +++ b/app/views/banners/edit.html.erb @@ -1,15 +1,19 @@
-
-
-

Edit <%= @banner.class.model_name.human %>

- <%= link_to 'View', banner_path(@banner), - class: "btn btn-secondary-outline" %> -
+
+
+
+
+

Edit <%= @banner.class.model_name.human %>

+ <%= link_to "View", banner_path(@banner), + class: "btn btn-secondary-outline" %> +
-
-
- <%= render "form", banner: @banner %> +
+
+ <%= render "form", banner: @banner %> +
+
diff --git a/app/views/banners/index.html.erb b/app/views/banners/index.html.erb index e53746f15..4bfa48934 100644 --- a/app/views/banners/index.html.erb +++ b/app/views/banners/index.html.erb @@ -20,7 +20,7 @@ Content - Published? + Published Actions @@ -30,7 +30,7 @@ <%= h(banner.content) %> - <% if banner.show? %> + <% if banner.published? %> <% else %> -- diff --git a/app/views/banners/show.html.erb b/app/views/banners/show.html.erb index 2fe5373bd..95a8dacf0 100644 --- a/app/views/banners/show.html.erb +++ b/app/views/banners/show.html.erb @@ -30,11 +30,11 @@

Created by:

-

<%= @banner.created_by.full_name %> @ <%= @banner.created_at.strftime("%Y-%m-%d %I:%M %P") %>

+

<%= @banner.created_by&.full_name %> @ <%= @banner.created_at.strftime("%Y-%m-%d %I:%M %P") %>

Updated by:

-

<%= @banner.updated_by.full_name %> @ <%= @banner.updated_at.strftime("%Y-%m-%d %I:%M %P") %>

+

<%= @banner.updated_by&.full_name %> @ <%= @banner.updated_at.strftime("%Y-%m-%d %I:%M %P") %>

diff --git a/app/views/bookmarks/_personal_row.html.erb b/app/views/bookmarks/_personal_row.html.erb index e70e08220..eef30e7f1 100644 --- a/app/views/bookmarks/_personal_row.html.erb +++ b/app/views/bookmarks/_personal_row.html.erb @@ -3,7 +3,7 @@
border-b + flex flex-col md:flex-row <%= "admin-only bg-blue-100" unless bookmarkable.published? %> border-b border-gray-200 list-group-item-custom gap-4 py-4 mb-0">
<% end %> - <%= link_to "#{bookmarkable.title}#{ type_name }#{" [HIDDEN]" if bookmarkable.inactive?}", + <%= link_to "#{bookmarkable.title}#{ type_name }#{" [UNPUBLISHED]" unless bookmarkable.published?}", polymorphic_path(bookmarkable), class: "hover:underline", data: { turbo: false, } %> diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index e91cb569a..637bc5c21 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -42,7 +42,6 @@
<%= f.input :published, as: :boolean, - label: "Published?", wrapper_html: { class: "flex items-center gap-2" }, input_html: { class: "form-checkbox" } %>
diff --git a/app/views/categories/_search_boxes.html.erb b/app/views/categories/_search_boxes.html.erb index d7393bc80..a9b60fead 100644 --- a/app/views/categories/_search_boxes.html.erb +++ b/app/views/categories/_search_boxes.html.erb @@ -34,10 +34,10 @@ - <%= select_tag :published_search, + <%= select_tag :published, options_for_select( [["All", ""], ["Published", "true"], ["Hidden", "false"]], - params[:published_search] + params[:published] ), class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full", onchange: "this.form.requestSubmit()" %> diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb index f3af8bc60..9efb96ca1 100644 --- a/app/views/categories/edit.html.erb +++ b/app/views/categories/edit.html.erb @@ -1,15 +1,19 @@
-
-
-

Edit <%= @category.class.model_name.human %>

- <%= link_to "Taggings", taggings_path(category_names: @category.name), - class: "btn btn-secondary-outline" %> -
+
+
+
+
+

Edit <%= @category.class.model_name.human %>

+ <%= link_to "Taggings", taggings_path(category_names: @category.name), + class: "btn btn-secondary-outline" %> +
-
-
- <%= render "form", category: @category %> +
+
+ <%= render "form", category: @category %> +
+
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb index a36ad1d66..69ed15228 100644 --- a/app/views/categories/index.html.erb +++ b/app/views/categories/index.html.erb @@ -52,7 +52,7 @@ data-controller="sortable" data-sortable-url-value="<%= category_path(id: ":id") %>"> <% @categories.each do |category| %> - <%= category.published ? "hover:bg-gray-50" : "hover:bg-gray-100" %> + <%= category.published? ? "hover:bg-gray-100" : "hover:bg-gray-50" %> transition-colors duration-150" data-sortable-id="<%= category.id %>"> @@ -69,13 +69,13 @@ <% if category.published? %> - - Yes - + + Yes + <% else %> - - No - + + No + <% end %> diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb index a025e174b..bc3b53327 100644 --- a/app/views/categories/new.html.erb +++ b/app/views/categories/new.html.erb @@ -6,7 +6,6 @@

New <%= @category.class.model_name.human %>

-
<%= render "form", category: @category %> @@ -14,6 +13,5 @@
-
\ No newline at end of file diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb index b99cee6a3..cfd9f9ab3 100644 --- a/app/views/categories/show.html.erb +++ b/app/views/categories/show.html.erb @@ -30,7 +30,7 @@

Published:

-

<%= @category.published %>

+

<%= @category.published? %>

diff --git a/app/views/community_news/_form.html.erb b/app/views/community_news/_form.html.erb index 28355f9b8..01190e5d3 100644 --- a/app/views/community_news/_form.html.erb +++ b/app/views/community_news/_form.html.erb @@ -13,8 +13,6 @@ <%# if allowed_to?(:manage?, @commenity_news) %>
<%= f.input :published, as: :boolean %> - - <%# f.input :inactive, as: :boolean, label: "Hidden?" %> <%= f.input :featured, as: :boolean, label: "Featured?" %> <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?" %> <%= f.input :publicly_featured, as: :boolean, label: "Publicly featured?" %> diff --git a/app/views/community_news/_search_boxes.html.erb b/app/views/community_news/_search_boxes.html.erb index db584fc71..b10d416fc 100644 --- a/app/views/community_news/_search_boxes.html.erb +++ b/app/views/community_news/_search_boxes.html.erb @@ -26,17 +26,17 @@ focus:ring-blue-500 focus:border-blue-500" %>
- <% if current_user.super_user? %> + <% if current_user&.super_user? %>
- <%= select_tag :published_search, + <%= select_tag :published, options_for_select( [["All", ""], ["Published", "true"], ["Hidden", "false"]], - params[:published_search] + params[:published] ), class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full", onchange: "this.form.requestSubmit()" %> diff --git a/app/views/community_news/edit.html.erb b/app/views/community_news/edit.html.erb index 63d8e26e7..064621373 100644 --- a/app/views/community_news/edit.html.erb +++ b/app/views/community_news/edit.html.erb @@ -1,24 +1,23 @@
-
-
-

Edit <%= @community_news.class.model_name.human %>

+
+
+
+
+

Edit <%= @community_news.class.model_name.human %>

- <%= link_to "View", community_news_path(@community_news), - class: "btn btn-secondary-outline" %> -
+ <%= link_to "View", community_news_path(@community_news), + class: "btn btn-secondary-outline" %> +
-
-
<%= render "form", community_news: @community_news %>
+
+
<%= render "form", community_news: @community_news %>
- <%= turbo_frame_tag "editor_assets_lazy", src: edit_community_news_path(@community_news) do %> - <%= render "shared/loading" %> - <% end %> + <%= turbo_frame_tag "editor_assets_lazy", src: edit_community_news_path(@community_news) do %> + <%= render "shared/loading" %> + <% end %> +
+
diff --git a/app/views/community_news/index.html.erb b/app/views/community_news/index.html.erb index 44d4d9ffa..bc02cd1da 100644 --- a/app/views/community_news/index.html.erb +++ b/app/views/community_news/index.html.erb @@ -6,7 +6,7 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New Post", new_community_news_path, class: "admin-only bg-blue-100 btn btn-primary-outline" %> diff --git a/app/views/community_news/new.html.erb b/app/views/community_news/new.html.erb index e3650c023..7a60c7f8e 100644 --- a/app/views/community_news/new.html.erb +++ b/app/views/community_news/new.html.erb @@ -1,15 +1,20 @@ -
+
-
-
-

New <%= @community_news.class.model_name.human %>

-
+
+
+
+
+

New <%= @community_news.class.model_name.human %>

+
-
+
-
-
- <%= render "form", community_news: @community_news %> +
+
+ <%= render "form", community_news: @community_news %> +
+
diff --git a/app/views/event_registrations/edit.html.erb b/app/views/event_registrations/edit.html.erb index 6e6b58ca6..dba6e43b5 100644 --- a/app/views/event_registrations/edit.html.erb +++ b/app/views/event_registrations/edit.html.erb @@ -1,18 +1,22 @@ -
-
+
+
+
+
+
+ +
+

Edit Event Registration

+ <%= link_to 'View', event_registration_path(@event_registration), class: "btn btn-secondary-outline" %> +
- -
-

Edit Event Registration

- <%= link_to 'View', event_registration_path(@event_registration), class: "btn btn-secondary-outline" %> -
- -
+
- -
- <%= render "form", event_registration: @event_registration %> + +
+ <%= render "form", event_registration: @event_registration %> +
+
+
-
diff --git a/app/views/event_registrations/index.html.erb b/app/views/event_registrations/index.html.erb index e2b1f4124..cad041f53 100644 --- a/app/views/event_registrations/index.html.erb +++ b/app/views/event_registrations/index.html.erb @@ -1,91 +1,87 @@ -
+
-
-
- -
-

- <%= EventRegistration.model_name.human.pluralize %> (<%= @event_registrations_count %>) -

-
- <% if current_user.super_user? %> - <%= link_to "New #{EventRegistration.model_name.human.downcase}", - new_event_registration_path, - class: "admin-only bg-blue-100 btn btn-primary-outline" %> - <% end %> +
+
+ +
+

+ <%= EventRegistration.model_name.human.pluralize %> (<%= @event_registrations_count %>) +

+
+ <% if current_user.super_user? %> + <%= link_to "New #{EventRegistration.model_name.human.downcase}", + new_event_registration_path, + class: "admin-only bg-blue-100 btn btn-primary-outline" %> + <% end %> +
-
- - <%= render "search_boxes" %> + + <%= render "search_boxes" %> -
- - - - - - - - - - +
+
+
EventEvent dateRegistrantRegistered atActions
+ + + + + + + + - - <% @event_registrations.each do |event_registration| %> - - - - - + + <% @event_registrations.each do |event_registration| %> + + + + - - - - <% end %> - -
RegistrantEventRegistered atActions
- <%= link_to event_registration.event.name.truncate(50), - event_path(event_registration.event), - class: "btn btn-secondary-outline whitespace-nowrap" %> - - <%= event_registration.event.decorate.times(display_day: true, display_date: true).html_safe %> - - <%# FIXME not all users have facilitators %> - <% if event_registration.registrant.facilitator %> - <%= link_to event_registration.registrant.full_name, - facilitator_path(event_registration.registrant.facilitator), - class: "btn btn-secondary-outline" %> - <% else %> - <%= event_registration.registrant.full_name %> - <% end %> - - <%= event_registration.created_at.strftime("%B %d, %Y") %> -
+ <%# FIXME not all users have facilitators %> + <% if event_registration.registrant.facilitator %> + <%= link_to event_registration.registrant.full_name, + facilitator_path(event_registration.registrant.facilitator), + class: "btn btn-secondary-outline" %> + <% else %> + <%= event_registration.registrant.full_name %> + <% end %> + + <%= link_to event_registration.event.name.truncate(50), + event_path(event_registration.event), + class: "btn btn-secondary-outline whitespace-nowrap" %> +
+ <%= event_registration.event.decorate.times(display_day: true, display_date: true).html_safe %> +
+ <%= event_registration.created_at.strftime("%B %d, %Y") %> + - <%= link_to "Edit", edit_event_registration_path(event_registration), - class: "btn btn-secondary-outline" %> -
-
+ + + <%= link_to "Edit", edit_event_registration_path(event_registration), + class: "btn btn-secondary-outline" %> + + + <% end %> + + +
- - <% unless @event_registrations.any? %> -

No <%= EventRegistration.model_name.human.pluralize %> found.

- <% end %> + + <% unless @event_registrations.any? %> +

No <%= EventRegistration.model_name.human.pluralize %> found.

+ <% end %> - -
-
+
-
diff --git a/app/views/event_registrations/new.html.erb b/app/views/event_registrations/new.html.erb index 905511f09..cc7c60757 100644 --- a/app/views/event_registrations/new.html.erb +++ b/app/views/event_registrations/new.html.erb @@ -1,9 +1,18 @@ -
-
-

New Event Registration

-
-
- <%= render "form", event_registration: @event_registration %> +
+
+
+
+
+

New Event Registration

+
+ +
+
+ <%= render "form", event_registration: @event_registration %> +
+
+
-
+
\ No newline at end of file diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index df238daec..540f031d7 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -21,11 +21,11 @@
<% if allowed_to?(:manage?, @event) %> -
- <%= f.input :inactive, as: :boolean, label: "Hidden?" %> - <%= f.input :featured, as: :boolean, label: "Featured?" %> - <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?" %> - <%= f.input :publicly_featured, as: :boolean, label: "Publicly featured?" %> +
+ <%= f.input :published, as: :boolean %> + <%= f.input :featured, as: :boolean %> + <%= f.input :publicly_visible, as: :boolean %> + <%= f.input :publicly_featured, as: :boolean %>
<% end %>
diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb index 68a7da7c7..ab2a79039 100644 --- a/app/views/events/edit.html.erb +++ b/app/views/events/edit.html.erb @@ -4,7 +4,7 @@

Edit Event

- <%= link_to 'View', event_path(@event), class: "btn btn-secondary-outline" %> + <%= link_to "View", event_path(@event), class: "btn btn-secondary-outline" %>
diff --git a/app/views/facilitators/_form.html.erb b/app/views/facilitators/_form.html.erb index 75de989e7..7e8550a90 100644 --- a/app/views/facilitators/_form.html.erb +++ b/app/views/facilitators/_form.html.erb @@ -22,30 +22,40 @@ class: "w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" } %> - <% if current_user.super_user? %> - <%= f.input :member_since, - as: :date, - discard_day: true, - start_year: 1991, - end_year: Time.current.year, - order: [:month, :year], - label: "Facilitator since", - hint: "Pick Jan if you only know the year", - input_html: { - type: "date", - class: "w-full rounded-md border-gray-300 shadow-sm admin-only bg-blue-100 border-blue-200 - focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" - } %> - <% else %> -
- - - <%= f.object.member_since&.to_date&.strftime("%B %d, %Y") || "—" %> - -
- <% end %> + <% if current_user.super_user? %> + <%= f.input :member_since, + as: :date, + discard_day: true, + start_year: 1991, + end_year: Time.current.year, + order: [:month, :year], + label: "Facilitator since", + hint: "Pick Jan if you only know the year", + input_html: { + type: "date", + class: "w-full rounded-md border-gray-300 shadow-sm admin-only bg-blue-100 border-blue-200 + focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" + } %> + <% else %> +
+ + + <%= f.object.member_since&.to_date&.strftime("%B %d, %Y") || "—" %> + +
+ <% end %> + <% if current_user.admin? %> +
+ <%= f.input :published, + as: :boolean, + input_html: { + class: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" + }, + wrapper_html: { class: "flex items-center space-x-2" } %> +
+ <% end %>
diff --git a/app/views/facilitators/edit.html.erb b/app/views/facilitators/edit.html.erb index 9889d4f14..9996d7dac 100644 --- a/app/views/facilitators/edit.html.erb +++ b/app/views/facilitators/edit.html.erb @@ -1,37 +1,37 @@ -
-
-
-
-

Edit <%= @facilitator.class.model_name.human %>

-
- <% if current_user.super_user? || current_user == @facilitator.user %> - <%= link_to "Change password", - change_password_path, - class: "btn btn-secondary-outline" %> - <% end %> - <% if @facilitator.user %> - <%= link_to "User account", - edit_user_path(@facilitator.user), - class: "admin-only bg-blue-100 btn btn-secondary-outline" %> - <% else %> - <%= link_to "Create user account", - new_user_path(facilitator_id: @facilitator.id), - class: "admin-only bg-blue-100 btn btn-secondary-outline" %> - <% end %> - <%= link_to "Profile", - facilitator_path(@facilitator), +
+
+
+

Edit <%= @facilitator.class.model_name.human %>

+
+ <% if current_user.super_user? || current_user == @facilitator.user %> + <%= link_to "Change password", + change_password_path, class: "btn btn-secondary-outline" %> -
+ <% end %> + <% if @facilitator.user %> + <%= link_to "User account", + edit_user_path(@facilitator.user), + class: "admin-only bg-blue-100 btn btn-secondary-outline" %> + <% else %> + <%= link_to "Create user account", + new_user_path(facilitator_id: @facilitator.id), + class: "admin-only bg-blue-100 btn btn-secondary-outline" %> + <% end %> + <%= link_to "Profile", + facilitator_path(@facilitator), + class: "btn btn-secondary-outline" %>
+
-
+
-
-
- <%= render "form", facilitator: @facilitator %> -
+
+
+ <%= render "form", facilitator: @facilitator %>
+
+ diff --git a/app/views/faqs/_faq.html.erb b/app/views/faqs/_faq.html.erb index ccff84d6b..11595ed10 100644 --- a/app/views/faqs/_faq.html.erb +++ b/app/views/faqs/_faq.html.erb @@ -19,18 +19,18 @@
- <%= render "inline_edit", model: faq, attribute: :inactive, form_class: "min-w-44" do %> + <%= render "inline_edit", model: faq, attribute: :published, form_class: "min-w-44" do %> <%= button_to faq_path(faq), - method: :patch, - params: { faq: { inactive: !faq.inactive } }, - class: "cursor-pointer pl-4" do %> - <% if faq.inactive? %> - - <% else %> + method: :patch, + params: { faq: { published: !faq.published? } }, + class: "cursor-pointer pl-4" do %> + <% if faq.published? %> + <% else %> + <% end %> <% end %> - <%= faq.inactive? ? "[HIDDEN]" : "[PUBLISHED]" %> + <%= faq.published? ? "[PUBLISHED]" : "[UNPUBLISHED]" %> <% end %> @@ -43,9 +43,7 @@ + peer-has-hover:shadow-sm peer-has-hover:rounded-md peer-has-hover:p-1"> <%= faq.question %> diff --git a/app/views/faqs/_form.html.erb b/app/views/faqs/_form.html.erb index ced84ecd4..b8c17f110 100644 --- a/app/views/faqs/_form.html.erb +++ b/app/views/faqs/_form.html.erb @@ -26,8 +26,8 @@
- <%= render "inline_fields", form: f, attribute: :inactive do %> - <%= f.input :inactive, label: "Hidden?" %> + <%= render "inline_fields", form: f, attribute: :published do %> + <%= f.input :published %> <% end %>
diff --git a/app/views/faqs/_search_boxes.html.erb b/app/views/faqs/_search_boxes.html.erb index dcf63ed72..85a3e03da 100644 --- a/app/views/faqs/_search_boxes.html.erb +++ b/app/views/faqs/_search_boxes.html.erb @@ -18,16 +18,16 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %>
- <%= label_tag :inactive, "Hidden?", class: "block text-sm font-medium text-gray-700 mb-1" %> - <%= select_tag :inactive, + <%= label_tag :published, "Published", class: "block text-sm font-medium text-gray-700 mb-1" %> + <%= select_tag :published, options_for_select([["All FAQs", ""], - ["Hidden FAQs", true], - ["Published FAQs", false]], - params[:inactive]), + ["Unpublished FAQs", false], + ["Published FAQs", true]], + params[:published]), class: "w-full rounded-lg border border-gray-300 px-3 py-2 text-gray-800 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:outline-none", onchange: "this.form.submit()" %> diff --git a/app/views/faqs/index.html.erb b/app/views/faqs/index.html.erb index 768f30fec..0aa42aee0 100644 --- a/app/views/faqs/index.html.erb +++ b/app/views/faqs/index.html.erb @@ -8,7 +8,7 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New FAQ", new_faq_path, class: "admin-only bg-blue-100 btn btn-primary-outline" %> @@ -26,8 +26,7 @@ class="mt-6 bg-white rounded-xl space-y-4 animate-fade p-3" data-controller="sortable" data-sortable-url-value="<%= faq_path(id: ":id") %>" - data-testid="faq-list" - > + data-testid="faq-list"> <% if @faqs.present? %> <%= render partial: "faq", collection: @faqs, as: :faq %> diff --git a/app/views/project_statuses/_form.html.erb b/app/views/project_statuses/_form.html.erb index 49eeb0cef..10f1b556d 100644 --- a/app/views/project_statuses/_form.html.erb +++ b/app/views/project_statuses/_form.html.erb @@ -1,17 +1,26 @@ <%= simple_form_for(@project_status) do |f| %> -
- +
+ + <%= f.error_notification %> <%= render "shared/errors", resource: @project_status if @project_status.errors.any? %> - -
- <% @project_status.attribute_names.reject { |a| ["id", "created_at", "updated_at"].include?(a) }.each do |attr| %> - <% next if ["id", "created_at", "updated_at"].include?(attr) %> -
- <%= f.input attr.to_sym, input_html: { class: "form-control" } %> -
- <% end %> + +
+ + <%= f.input :name, + label: "Status Name", + input_html: { class: "form-control" }, + required: true %> + +
+ <%= f.input :published, + as: :boolean, + label: "Published?", + input_html: { class: "h-4 w-4" }, + wrapper_html: { class: "mb-0" } %> +
+
diff --git a/app/views/projects/_form.html.erb b/app/views/projects/_form.html.erb index bcc89fb4a..b56a8fa3f 100644 --- a/app/views/projects/_form.html.erb +++ b/app/views/projects/_form.html.erb @@ -34,9 +34,8 @@
- <%= f.input :inactive, + <%= f.input :published, as: :boolean, - label: "Hidden?", input_html: { class: "mr-2 rounded focus:ring-blue-500 text-blue-600" } %> diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb index aa1f2637a..cd59c1af3 100644 --- a/app/views/projects/edit.html.erb +++ b/app/views/projects/edit.html.erb @@ -3,7 +3,7 @@

Edit Organization

- <%= link_to 'View', project_path(@project), + <%= link_to "View", project_path(@project), class: "btn btn-secondary-outline" %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index b30eb889a..92164c33b 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -87,10 +87,10 @@ - <% if project.inactive? %> - - <% else %> + <% if project.published? %> + <% else %> + <% end %> diff --git a/app/views/quotes/_form.html.erb b/app/views/quotes/_form.html.erb index b4c28a9f7..2e30a41be 100644 --- a/app/views/quotes/_form.html.erb +++ b/app/views/quotes/_form.html.erb @@ -57,9 +57,8 @@
- <%= f.input :inactive, + <%= f.input :published, as: :boolean, - label: "Hidden?", wrapper_html: { class: "flex items-center" }, input_html: { class: "h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500" diff --git a/app/views/quotes/index.html.erb b/app/views/quotes/index.html.erb index 9cb4aa071..652ef8b0d 100644 --- a/app/views/quotes/index.html.erb +++ b/app/views/quotes/index.html.erb @@ -29,12 +29,12 @@
- <% if current_user.super_user? && quote.inactive? && controller_name != "dashboard" %> + <% if current_user.super_user? && !quote.published? && controller_name != "dashboard" %> - Hidden + Unpublished <% end %> diff --git a/app/views/quotes/show.html.erb b/app/views/quotes/show.html.erb index 359184e31..cb804b622 100644 --- a/app/views/quotes/show.html.erb +++ b/app/views/quotes/show.html.erb @@ -48,15 +48,15 @@

Status:

- <% if @quote.inactive? %> + <% if !@quote.published? %> - Hidden + Unpublished <% else %> - Active + Published <% end %>
diff --git a/app/views/resources/_form.html.erb b/app/views/resources/_form.html.erb index 57c12a751..1a4ec201e 100644 --- a/app/views/resources/_form.html.erb +++ b/app/views/resources/_form.html.erb @@ -18,10 +18,10 @@ <% if allowed_to?(:manage?, @resource) %>
- <%= f.input :inactive, as: :boolean, label: "Hidden?" %> - <%= f.input :featured, as: :boolean, label: "Featured?" %> - <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?" %> - <%= f.input :publicly_featured, as: :boolean, label: "Publicly featured?" %> + <%= f.input :published, as: :boolean %> + <%= f.input :featured, as: :boolean %> + <%= f.input :publicly_visible, as: :boolean %> + <%= f.input :publicly_featured, as: :boolean %>
<% end %>
diff --git a/app/views/resources/_search_boxes.html.erb b/app/views/resources/_search_boxes.html.erb index fb2fcbbe4..940329bb6 100644 --- a/app/views/resources/_search_boxes.html.erb +++ b/app/views/resources/_search_boxes.html.erb @@ -30,16 +30,16 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %>
- <%= select_tag :published_search, + <%= select_tag :published, options_for_select( [["All", ""], ["Published", "true"], ["Hidden", "false"]], - params[:published_search] + params[:published] ), class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full" %>
diff --git a/app/views/sectors/_form.html.erb b/app/views/sectors/_form.html.erb index 529d7627a..c9d14f106 100644 --- a/app/views/sectors/_form.html.erb +++ b/app/views/sectors/_form.html.erb @@ -18,7 +18,6 @@
<%= f.input :published, as: :boolean, - label: "Published?", wrapper_html: { class: "flex items-center gap-2" }, input_html: { class: "form-checkbox" } %>
diff --git a/app/views/sectors/_search_boxes.html.erb b/app/views/sectors/_search_boxes.html.erb index 197763031..8ec02cdb8 100644 --- a/app/views/sectors/_search_boxes.html.erb +++ b/app/views/sectors/_search_boxes.html.erb @@ -18,11 +18,11 @@
- <%= f.label :published_search, "Published", + <%= f.label :published, "Published", class: "block text-sm font-medium text-gray-700" %> - <%= f.select :published_search, - options_for_select([["All", ""], ["Published", "true"], ["Hidden", "false"]], params[:published_search]), + <%= f.select :published, + options_for_select([["All", ""], ["Published", "true"], ["Hidden", "false"]], params[:published]), {}, class: "mt-1 block w-full rounded-md border border-gray-300 p-2", onchange: "this.form.requestSubmit()" %> diff --git a/app/views/sectors/index.html.erb b/app/views/sectors/index.html.erb index d5e052ff5..c14814206 100644 --- a/app/views/sectors/index.html.erb +++ b/app/views/sectors/index.html.erb @@ -39,8 +39,8 @@ <% @sectors.each do |sector| %> - - <%= sector.published ? "hover:bg-gray-50" : "hover:bg-gray-100" %> transition-colors duration-150"> + + <%= sector.published? ? "hover:bg-gray-100" : "hover:bg-gray-50" %> transition-colors duration-150"> <%= sector.name %> diff --git a/app/views/sectors/show.html.erb b/app/views/sectors/show.html.erb index d151b2925..d954470b4 100644 --- a/app/views/sectors/show.html.erb +++ b/app/views/sectors/show.html.erb @@ -26,7 +26,7 @@

Published:

-

<%= @sector.published %>

+

<%= @sector.published? %>

diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index c96d303bc..b9bb70ab6 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -14,11 +14,9 @@ <%# if allowed_to?(:manage?, story) %>
<%= f.input :published, as: :boolean %> - - <%# f.input :inactive, as: :boolean, label: "Hidden?" %> - <%= f.input :featured, as: :boolean, label: "Featured?" %> - <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?" %> - <%= f.input :publicly_featured, as: :boolean, label: "Publicly featured?" %> + <%= f.input :featured, as: :boolean %> + <%= f.input :publicly_visible, as: :boolean %> + <%= f.input :publicly_featured, as: :boolean %>
<%# end %> diff --git a/app/views/stories/_search_boxes.html.erb b/app/views/stories/_search_boxes.html.erb index 4072e2cc2..570cc6eb0 100644 --- a/app/views/stories/_search_boxes.html.erb +++ b/app/views/stories/_search_boxes.html.erb @@ -28,17 +28,17 @@ focus:ring-blue-500 focus:border-blue-500" %>
- <% if current_user.super_user? %> + <% if current_user&.super_user? %>
- <%= select_tag :published_search, + <%= select_tag :published, options_for_select( [["All", ""], ["Published", "true"], ["Hidden", "false"]], - params[:published_search] + params[:published] ), class: "border border-gray-300 rounded-lg px-3 py-2 text-gray-700 w-full", onchange: "this.form.requestSubmit()" %> diff --git a/app/views/stories/index.html.erb b/app/views/stories/index.html.erb index 83a1d7fa2..98e560134 100644 --- a/app/views/stories/index.html.erb +++ b/app/views/stories/index.html.erb @@ -6,7 +6,7 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New Story", new_story_path, class: "admin-only bg-blue-100 btn btn-primary-outline" %> diff --git a/app/views/taggings/index.html.erb b/app/views/taggings/index.html.erb index 8f8fb4d82..c97bcbb8e 100644 --- a/app/views/taggings/index.html.erb +++ b/app/views/taggings/index.html.erb @@ -1,9 +1,13 @@ <% sector_names = @sector_names.to_s.split("--").join(", ") %> <% category_full_names = - Category.names(@category_names) - .includes(:category_type) - .map { |c| "#{c.category_type&.name&.titleize}: #{c.name}" } - .join(", ") + if @category_names.present? + Category.names(@category_names) + .includes(:category_type) + .map { |c| "#{c.category_type&.name&.titleize}: #{c.name}" } + .join(", ") + else + nil + end %>
diff --git a/app/views/tutorials/_search_boxes.html.erb b/app/views/tutorials/_search_boxes.html.erb index 5d2772860..ab452c81a 100644 --- a/app/views/tutorials/_search_boxes.html.erb +++ b/app/views/tutorials/_search_boxes.html.erb @@ -18,11 +18,11 @@ <% if current_user.super_user? %>
- <%= f.label :published_search, "Published", + <%= f.label :published, "Published", class: "block text-sm font-medium text-gray-700" %> - <%= f.select :published_search, - options_for_select([["All", ""], ["Published", "true"], ["Hidden", "false"]], params[:published_search]), + <%= f.select :published, + options_for_select([["All", ""], ["Published", "true"], ["Hidden", "false"]], params[:published]), {}, class: "mt-1 block w-full rounded-md border border-gray-300 p-2", onchange: "this.form.requestSubmit()" %> diff --git a/app/views/tutorials/index.html.erb b/app/views/tutorials/index.html.erb index a46872979..040bdc571 100644 --- a/app/views/tutorials/index.html.erb +++ b/app/views/tutorials/index.html.erb @@ -12,7 +12,7 @@
- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New #{Tutorial.model_name.human.downcase}", new_tutorial_path, class: "admin-only bg-blue-100 btn btn-primary-outline" %> diff --git a/app/views/users/_search_boxes.html.erb b/app/views/users/_search_boxes.html.erb index b5c575a91..9418ba333 100644 --- a/app/views/users/_search_boxes.html.erb +++ b/app/views/users/_search_boxes.html.erb @@ -36,7 +36,7 @@
- <%= label_tag :inactive, "Hidden?", class: "block text-sm font-medium text-gray-700 mb-1" %> + <%= label_tag :inactive, class: "block text-sm font-medium text-gray-700 mb-1" %> <%= select_tag :inactive, options_for_select([["All users", ""], ["Inactive users", true], diff --git a/app/views/workshop_variations/_form.html.erb b/app/views/workshop_variations/_form.html.erb index a30f837fe..00a84842d 100644 --- a/app/views/workshop_variations/_form.html.erb +++ b/app/views/workshop_variations/_form.html.erb @@ -43,7 +43,7 @@ <% if current_user.super_user? %>
- <%= f.input :inactive, as: :boolean, label: "Hidden?", input_html: { checked: f.object.id ? f.object.inactive : false } %> + <%= f.input :published, as: :boolean, input_html: { checked: f.object.id ? f.object.published? : false } %> <%= f.input :position, as: :integer, label: "Ordering", input_html: { class: "w-24" } %> diff --git a/app/views/workshop_variations/show.html.erb b/app/views/workshop_variations/show.html.erb index 7bbe12abb..236a7abfb 100644 --- a/app/views/workshop_variations/show.html.erb +++ b/app/views/workshop_variations/show.html.erb @@ -28,9 +28,9 @@

<%= @workshop_variation.name %> - <% if @workshop_variation.inactive? %> + <% unless @workshop_variation.published? %> - [HIDDEN] + [UNPUBLISHED] <% end %>

diff --git a/app/views/workshops/_filters.html.erb b/app/views/workshops/_filters.html.erb index 43c4fb2c3..c336346c5 100644 --- a/app/views/workshops/_filters.html.erb +++ b/app/views/workshops/_filters.html.erb @@ -39,5 +39,5 @@ label_method: :short_name, param_name: :windows_types %> - <%= render "inactive_fields" if current_user.super_user? %> + <%= render "inactive_fields" if current_user&.super_user? %>
diff --git a/app/views/workshops/_filters_applied.html.erb b/app/views/workshops/_filters_applied.html.erb index 442354458..f481fa922 100644 --- a/app/views/workshops/_filters_applied.html.erb +++ b/app/views/workshops/_filters_applied.html.erb @@ -10,16 +10,16 @@ Sector.where(id: params[:sectors].to_unsafe_h.values.reject(&:blank?)) .pluck(:name) : [] %> -<% active_applied = "Published" if params[:active] == "true" %> +<% published_applied = "Published" if params[:published] == "true" %> -<% inactive_applied = "Hidden" if params[:inactive] == "true" %> +<% unpublished_applied = "Unpublished" if params[:published] == "true" %> <% filters_applied = [ *windows_types_applied, *categories_applied, *sections_applied, - active_applied, - inactive_applied, + published_applied, + unpublished_applied, "#{ "Title: " + params[:title] if params[:title].present? }", "#{ "Author: " + params[:author_name] if params[:author_name].present? }", "#{ "Full-text: " + params[:query] if params[:query].present? }", @@ -32,7 +32,7 @@ <% if filters_applied.any? %> <%= filters_applied.to_sentence %> - <%= link_to 'Clear filters', workshops_path, class: "btn btn-primary" %> + <%= link_to "Clear filters", workshops_path, class: "btn btn-primary" %> <% else %> None <% end %> diff --git a/app/views/workshops/_form.html.erb b/app/views/workshops/_form.html.erb index 1ed900b52..6cff57ef2 100644 --- a/app/views/workshops/_form.html.erb +++ b/app/views/workshops/_form.html.erb @@ -79,10 +79,10 @@ <%# if allowed_to?(:manage?, @workshop) %>
- <%= f.input :inactive, as: :boolean, label: "Hidden?" %> - <%= f.input :featured, as: :boolean, label: "Featured?" %> - <%= f.input :publicly_visible, as: :boolean, label: "Publicly visible?" %> - <%= f.input :publicly_featured, as: :boolean, label: "Publicly featured?" %> + <%= f.input :published, as: :boolean %> + <%= f.input :featured, as: :boolean %> + <%= f.input :publicly_visible, as: :boolean %> + <%= f.input :publicly_featured, as: :boolean %>
<%# end %> diff --git a/app/views/workshops/_inactive_fields.html.erb b/app/views/workshops/_inactive_fields.html.erb index 8d0e8135e..ad5c8fa1c 100644 --- a/app/views/workshops/_inactive_fields.html.erb +++ b/app/views/workshops/_inactive_fields.html.erb @@ -12,31 +12,22 @@ class=" admin-only bg-blue-100 relative z-10 w-full text-gray-800 py-2 px-4 shadow-sm rounded-md border border-gray-300 cursor-pointer flex justify-between - items-center - " - > + items-center"> - + - <% if workshop.inactive %> + <% unless workshop.published %>
<%= "***THIS WORKSHOP IS HIDDEN***" %>
<% end %> diff --git a/app/views/workshops/_show_header.html.erb b/app/views/workshops/_show_header.html.erb index 385ac4019..205cf5704 100644 --- a/app/views/workshops/_show_header.html.erb +++ b/app/views/workshops/_show_header.html.erb @@ -14,8 +14,8 @@

<%= workshop.title %> - <% if workshop.inactive %> - (Hidden) + <% unless workshop.published %> + (Unpublished) <% end %>

diff --git a/app/views/workshops/index.html.erb b/app/views/workshops/index.html.erb index dbd8d25cf..5001fb6ee 100644 --- a/app/views/workshops/index.html.erb +++ b/app/views/workshops/index.html.erb @@ -13,7 +13,7 @@

- <% if current_user.super_user? %> + <% if current_user&.super_user? %> <%= link_to "New workshop", new_workshop_path, class: "admin-only bg-blue-100 btn btn-primary-outline" %> diff --git a/app/views/workshops/workshop_results.html.erb b/app/views/workshops/workshop_results.html.erb index d384ac4bd..cb15dd2c0 100644 --- a/app/views/workshops/workshop_results.html.erb +++ b/app/views/workshops/workshop_results.html.erb @@ -5,9 +5,9 @@ <%= turbo_stream.replace("copy_url", partial: "shared/copy_url") %> -
+
<% if @workshops.any? %> -
+
<% @workshops.each do |workshop| %> <%= render "workshops/index_row", workshop: workshop.decorate, breadcrumb: "Workshops", link_route: workshop_path(workshop) %> diff --git a/db/migrate/20260205020718_add_published_to_tables.rb b/db/migrate/20260205020718_add_published_to_tables.rb new file mode 100644 index 000000000..50d6e0518 --- /dev/null +++ b/db/migrate/20260205020718_add_published_to_tables.rb @@ -0,0 +1,74 @@ +class AddPublishedToTables < ActiveRecord::Migration[8.1] + TABLES = %i[ + events + faqs + projects + project_obligations + project_statuses + quotes + resources + workshop_variations + workshops + + banners + facilitators + ] + # TABLES_WITH_PUBLISHED_ALREADY = %i[ + # # categories + # # category_types + # # community_news + # # sectors + # # stories + # # tutorials + + # Keeping inactive + # addresses + # contact_methods + # project_users + # users + + # Removing inactive entirely + # categorizable_items + # sectorable_items + + # TODO + # remove show from banners + # add inactive to locations + + def up + TABLES.each do |table| + next unless table_exists?(table) + next if column_exists?(table, :published) + + add_column table, :published, :boolean, null: false, default: false + add_index table, :published + + say_with_time "Backfilling #{table}.published" do + if column_exists?(table, :inactive) + execute <<~SQL.squish + UPDATE #{table} + SET published = CASE + WHEN inactive = false THEN true + ELSE false + END + SQL + else + # Table never had lifecycle → assume existing records are live + execute <<~SQL.squish + UPDATE #{table} + SET published = true + SQL + end + end + end + end + + def down + TABLES.each do |table| + next unless column_exists?(table, :published) + + remove_index table, :published if index_exists?(table, :published) + remove_column table, :published + end + end +end diff --git a/db/migrate/20260205032602_remove_inactive_from_taggings.rb b/db/migrate/20260205032602_remove_inactive_from_taggings.rb new file mode 100644 index 000000000..d0fb59094 --- /dev/null +++ b/db/migrate/20260205032602_remove_inactive_from_taggings.rb @@ -0,0 +1,12 @@ +class RemoveInactiveFromTaggings < ActiveRecord::Migration[8.1] + def change + # Remove inactive column from sectorable_items and categorizable_items bc they should either exist or not + + if column_exists?(:sectorable_items, :inactive) + remove_column :sectorable_items, :inactive + end + if column_exists?(:categorizable_items, :inactive) + remove_column :categorizable_items, :inactive + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5380e4519..32cb9d94b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_02_04_184617) do +ActiveRecord::Schema[8.1].define(version: 2026_02_05_032602) do create_table "action_text_mentions", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.bigint "action_text_rich_text_id", null: false t.datetime "created_at", null: false @@ -192,10 +192,12 @@ t.text "content", size: :medium t.datetime "created_at", precision: nil, null: false t.integer "created_by_id" + t.boolean "published", default: false, null: false t.boolean "show" t.datetime "updated_at", precision: nil, null: false t.integer "updated_by_id" t.index ["created_by_id"], name: "index_banners_on_created_by_id" + t.index ["published"], name: "index_banners_on_published" t.index ["updated_by_id"], name: "index_banners_on_updated_by_id" end @@ -233,7 +235,6 @@ t.string "categorizable_type" t.integer "category_id" t.datetime "created_at", precision: nil, null: false - t.boolean "inactive", default: true t.integer "legacy_id" t.datetime "updated_at", precision: nil, null: false t.index ["categorizable_type", "categorizable_id"], name: "idx_on_categorizable_type_categorizable_id_ccce65d80c" @@ -324,11 +325,13 @@ t.boolean "inactive", default: true, null: false t.boolean "publicly_featured", default: false, null: false t.boolean "publicly_visible", default: false, null: false + t.boolean "published", default: false, null: false t.datetime "registration_close_date", precision: nil t.datetime "start_date", precision: nil t.string "title" t.datetime "updated_at", null: false t.index ["created_by_id"], name: "index_events_on_created_by_id" + t.index ["published"], name: "index_events_on_published" end create_table "facilitators", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| @@ -366,11 +369,13 @@ t.boolean "profile_show_workshop_variations", default: true, null: false t.boolean "profile_show_workshops", default: true, null: false t.string "pronouns" + t.boolean "published", default: false, null: false t.string "twitter_url" t.datetime "updated_at", null: false t.integer "updated_by_id" t.string "youtube_url" t.index ["created_by_id"], name: "index_facilitators_on_created_by_id" + t.index ["published"], name: "index_facilitators_on_published" t.index ["updated_by_id"], name: "index_facilitators_on_updated_by_id" end @@ -380,8 +385,10 @@ t.boolean "inactive" t.integer "position", null: false t.boolean "publicly_visible", default: false, null: false + t.boolean "published", default: false, null: false t.string "question" t.datetime "updated_at", precision: nil, null: false + t.index ["published"], name: "index_faqs_on_published" end create_table "footers", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| @@ -542,13 +549,17 @@ create_table "project_obligations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false t.string "name" + t.boolean "published", default: false, null: false t.datetime "updated_at", precision: nil, null: false + t.index ["published"], name: "index_project_obligations_on_published" end create_table "project_statuses", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false t.string "name" + t.boolean "published", default: false, null: false t.datetime "updated_at", precision: nil, null: false + t.index ["published"], name: "index_project_statuses_on_published" end create_table "project_users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| @@ -582,12 +593,14 @@ t.string "name" t.text "notes", size: :long t.integer "project_status_id" + t.boolean "published", default: false, null: false t.date "start_date" t.datetime "updated_at", precision: nil, null: false t.string "website_url" t.integer "windows_type_id" t.index ["location_id"], name: "index_projects_on_location_id" t.index ["project_status_id"], name: "index_projects_on_project_status_id" + t.index ["published"], name: "index_projects_on_published" t.index ["windows_type_id"], name: "index_projects_on_windows_type_id" end @@ -608,10 +621,12 @@ t.boolean "inactive", default: true t.boolean "legacy", default: false t.integer "legacy_id" + t.boolean "published", default: false, null: false t.text "quote", size: :long t.string "speaker_name" t.datetime "updated_at", precision: nil, null: false t.integer "workshop_id" + t.index ["published"], name: "index_quotes_on_published" t.index ["workshop_id"], name: "index_quotes_on_workshop_id" end @@ -672,6 +687,7 @@ t.integer "position" t.boolean "publicly_featured", default: false, null: false t.boolean "publicly_visible", default: false, null: false + t.boolean "published", default: false, null: false t.text "text", size: :long t.string "title" t.datetime "updated_at", precision: nil, null: false @@ -679,6 +695,7 @@ t.integer "user_id" t.integer "windows_type_id" t.integer "workshop_id" + t.index ["published"], name: "index_resources_on_published" t.index ["user_id"], name: "index_resources_on_user_id" t.index ["windows_type_id"], name: "index_resources_on_windows_type_id" t.index ["workshop_id"], name: "index_resources_on_workshop_id" @@ -686,7 +703,6 @@ create_table "sectorable_items", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.datetime "created_at", precision: nil, null: false - t.boolean "inactive", default: true t.boolean "is_leader", default: false, null: false t.integer "sector_id" t.integer "sectorable_id" @@ -986,11 +1002,13 @@ t.boolean "legacy", default: false t.string "name" t.integer "position" + t.boolean "published", default: false, null: false t.datetime "updated_at", precision: nil, null: false t.integer "variation_id" t.integer "workshop_id" t.string "youtube_url" t.index ["created_by_id"], name: "index_workshop_variations_on_created_by_id" + t.index ["published"], name: "index_workshop_variations_on_published" t.index ["workshop_id"], name: "index_workshop_variations_on_workshop_id" end @@ -1047,6 +1065,7 @@ t.string "pub_issue" t.boolean "publicly_featured", default: false, null: false t.boolean "publicly_visible", default: false, null: false + t.boolean "published", default: false, null: false t.boolean "searchable", default: false t.text "setup", size: :long t.text "setup_spanish", size: :long @@ -1079,6 +1098,7 @@ t.index ["created_at"], name: "index_workshops_on_created_at" t.index ["inactive", "led_count", "title"], name: "index_workshops_on_inactive_and_led_count_and_title" t.index ["led_count"], name: "index_workshops_on_led_count" + t.index ["published"], name: "index_workshops_on_published" t.index ["title", "full_name", "objective", "materials", "introduction", "demonstration", "opening_circle", "warm_up", "creation", "closing", "notes", "tips", "misc1", "misc2"], name: "workshop_fullsearch", type: :fulltext t.index ["title"], name: "index_workshops_on_title", type: :fulltext t.index ["title"], name: "workshop_fullsearch_title", type: :fulltext diff --git a/db/seeds/dummy_dev_seeds.rb b/db/seeds/dummy_dev_seeds.rb index 9d5b4ef73..0c1cf14af 100644 --- a/db/seeds/dummy_dev_seeds.rb +++ b/db/seeds/dummy_dev_seeds.rb @@ -106,7 +106,7 @@ .first_or_create!( description: Faker::Lorem.paragraph(sentence_count: 6), featured: [ true, false ].sample, - inactive: [ false, false, true ].sample, + published: [ false, false, true ].sample, registration_close_date: registration_close, created_by_id: User.first.id, created_at: Time.current - rand(10..90).days, @@ -144,49 +144,49 @@ id: 1, question: "Why art?", answer: %( Art workshops provide a unique way to assist survivors of domestic violence in healing from the trauma of abuse, finding their voice, and building the courage to make healthy decisions for their future. For victims of domestic violence, art workshops provide a special "window" of support to share the complexity of their emotions, discover that they are not alone, and are not to blame for the violence. The art also helps survivors build healthy ways to handle anger and communicate non-violently. - ), inactive: false, ordering: 200 + ), published: true, ordering: 200 }, { id: 2, question: "What is the difference between the Windows Program and art therapy?", answer: %( The AWBW workshops offer a process of self-expression, self-exploration, and self-interpretation. Unlike art therapy, there is no therapist or other authority responsible for interpretation or diagnosis. Each participant is in charge of her own creative exploration. For women and children who have been living under the control of another human being for so long, a simple art experience can provide a powerful opportunity to notice for the first time that they have the freedom to decide what they want to create. By placing the authority in the hands of each participant, the Windows workshops create an environment where survivors effectively support each other and take leadership in finding their own solutions. - ), inactive: false, ordering: 190 + ), published: true, ordering: 190 }, { id: 4, question: "I work with survivors of domestic violence. How can I bring the AWBW Program to my organization?", answer: %{ We welcome you to implement the Adult Windows Program or the Youth Windows Program at your organization. AWBW provides a comprehensive two-day training program for new leaders at our office/studio in Venice, California, or in other locations nationwide (make this a link for non-local trainings?). Our training, facilitated by artists and experienced leaders, prepares you to bring the Windows Program to your organization. In some cases, we are also able to provide art supply allowances to help you get started. For all organizations that begin a Windows Program, AWBW provides permanent ongoing support including newsletters, leader's workshops, access to online leader support, and personalized consultation for as long as your program exists. Check for upcoming training opportunities. - }, inactive: false, ordering: 170 + }, published: true, ordering: 170 }, { id: 6, question: "How can I volunteer for A Window Between Worlds?", answer: %( Contact us! We have a wide variety of volunteer opportunities for anyone who is available to donate some time in the Los Angeles area. - ), inactive: false, ordering: 165 + ), published: true, ordering: 165 }, { id: 7, question: "I am a survivor. How can I participate in the art program?", answer: %( You are welcome to participate in our Survivor's Art Circle, which provides support and encouragement for any domestic violence survivor wishing to use art as a healing tool. If you are in the Los Angeles area, you can attend monthly hands-on workshops with other survivors. If you are not in Los Angeles, you are welcome to participate in the online community of support. As part of the Survivor's Art Circle you will receive a monthly newsflash, and will be welcome to participate in group project ideas you can complete at home. You will also be welcome to participate in Survivor's Art Circle exhibition opportunities. - ), inactive: false, ordering: 140 + ), published: true, ordering: 140 }, { id: 12, question: "How do I get a scholarship for Leadership Training?", answer: %( We award all scholarships based on need and availability of funds to agencies serving domestic violence clients. We ask those interested in applying for scholarship funding to submit a Scholarship Request 4 weeks in advance of the chosen training. Click here to see the guidelines. - ), inactive: false, ordering: 120 + ), published: true, ordering: 120 }, { id: 13, question: "I need more art supplies to hold my Windows Workshops. How can AWBW help?", answer: %( AWBW awards Art Supply Scholarships to active reporting programs. All scholarship grants are based on need, availability of funds and strength of monthly reporting. Programs must report for a minimum of three months to be eligible to receive an art supply scholarship and must continue to hold weekly workshops and report monthly for a period of one year.

AWBW programs that have been awarded art supply scholarships will be reimbursed for art supplies bought at any purveyor of their choosing as long as they submit receipts attached to AWBW’s reimbursement form.

Visit our recommended supply resource list for information on where you can order art supplies.

AWBW also offers some free art supplies from our donated goods shopping area. Programs in good standing can make an appointment to “free shop” at our Venice location. - ), inactive: false, ordering: 110 + ), published: true, ordering: 110 }, { id: 5, question: "I would like to volunteer to run art workshops at my local shelter. How can I get involved?", answer: %( Due to confidentiality issues, art workshops are run by volunteers and staff who already work with a domestic violence agency, rather than outside volunteers. Contact your local domestic violence organization to find out about volunteer opportunities and whether or not they use the Windows Program. You will need to meet the individual agency’s training requirements and get their permission and support to implement AWBW's program. Resource numbers you can call to find domestic violence organizations in your local area. - ), inactive: false, ordering: 109 + ), published: true, ordering: 109 }, { id: 38, question: "Why Can We Train People Within Our Agency, But Not Train People Outside Our Agency?", @@ -194,7 +194,7 @@ 1. We encourage you to train others within your agency because we want you to be able to do all you can to help your Windows groups become as strong and creative as possible. Over the years we've seen that the AWBW trained leaders can teach others quite effectively, and the new leaders they train also become a wonderful asset to the program.
2. There is a special process for training the trainers (for training beyond one's own agency). Otherwise many people who've been to only one training might want to start representing AWBW, and leading throughout beyond their agency, and we'd have no way of knowing if they were effective. There would also be no system of connecting what they are doing into the AWBW network of leaders.
3. Everything we do has been made possible by the network of leaders communicating and staying in touch with AWBW. The program and all that's been developed simply wouldn't exist without all the leaders sending their reports, insights and thoughts to AWBW so we can share them with everybody. So by leading the trainings we are able to make sure new agencies get the best shot possible at being closely connected to this network, so that it can thrive and continue. - }, inactive: false, ordering: 108 + }, published: true, ordering: 108 }, { id: 39, question: "So How Can I Share AWBW With Other Agencies Within My Community?", @@ -204,23 +204,23 @@ 2. Encourage them to get trained (by either hosting a training, having distance learning, or coming to California :-)
3. Encourage them to take advantage of the scholarships we have through grant funding for the training in LA and the distance training.
4. If a leader works at your agency and then gets a job at another agency, they are welcome to contact us and let us know they are starting the program there. We will be happy to support them in starting a new program. - }, inactive: false, ordering: 107 + }, published: true, ordering: 107 }, { id: 80, question: "How do I get the teens to trust their school counselors?", answer: %( School counselors are not trained to be mandated reporters. Some counselors are friendly and some are not. Help the teens to ascertain which counselors are safe to talk to. - ), inactive: false, ordering: 5 + ), published: true, ordering: 5 }, { - id: 81, question: "Teen Dating FAQ’s", answer: "tbd", inactive: false, ordering: 14 + id: 81, question: "Teen Dating FAQ’s", answer: "tbd", published: true, ordering: 14 } ] faqs.each do |faq_data| Faq.find_or_initialize_by(id: faq_data[:id]).tap do |faq| faq.question = faq_data[:question] faq.answer = faq_data[:answer] - faq.inactive = faq_data[:inactive] + faq.published = faq_data[:published] faq.ordering = faq_data[:ordering] faq.save! end @@ -237,7 +237,7 @@ year: 1996, description: "Gives participants a window of time to think about what brings them comfort and to collage related images onto a journal.", tips: "

", - inactive: true, + published: false, searchable: true, created_at: Time.zone.parse("2005-03-01 02:45:02"), updated_at: Time.zone.parse("2015-10-19 19:19:04") @@ -251,7 +251,7 @@ year: 2009, description: "Helps children and teens discover more about their inner selves through mixed-media portrait creation.", tips: "Embodied art note…", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2009-08-25 14:50:19"), updated_at: Time.zone.parse("2014-07-31 12:32:27") @@ -264,7 +264,7 @@ month: 12, year: 1996, description: "A relaxing collage-based visioning activity for the upcoming year.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2005-03-01 02:45:02"), updated_at: Time.zone.parse("2012-11-29 18:00:09") @@ -277,7 +277,7 @@ month: 5, year: 2012, description: "Participants create mandalas focused on gratitude, calming the nervous system.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2012-05-11 14:00:00"), updated_at: Time.zone.parse("2015-02-01 09:22:10") @@ -290,7 +290,7 @@ month: 2, year: 2015, description: "Explores what safety looks and feels like through imagery and symbolism.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2015-02-02 10:10:10"), updated_at: Time.zone.parse("2016-01-20 08:01:22") @@ -303,7 +303,7 @@ month: 4, year: 2017, description: "Youth identify, draw, and claim their inner strengths using shield symbolism.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2017-04-21 11:20:00"), updated_at: Time.zone.parse("2018-09-14 13:33:55") @@ -316,7 +316,7 @@ month: 3, year: 2018, description: "Participants explore emotional landscapes by drawing and labeling heart maps.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2018-03-14 09:40:00"), updated_at: Time.zone.parse("2019-05-11 16:02:10") @@ -329,7 +329,7 @@ month: 8, year: 2014, description: "A sensory activity that teaches emotional regulation through glitter jars.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2014-08-08 12:12:00"), updated_at: Time.zone.parse("2016-06-10 12:12:10") @@ -342,7 +342,7 @@ month: 5, year: 2020, description: "Participants create symbolic flags representing identity, hopes, and values.", - inactive: false, + published: true, searchable: true, created_at: Time.zone.parse("2020-05-03 12:10:00"), updated_at: Time.zone.parse("2021-02-14 10:10:10") @@ -354,7 +354,7 @@ month: 6, year: 2019, description: "Exploring hope through creative collage.", notes: "", tips: "", pub_issue: "V/6", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2019-06-01"), updated_at: Time.zone.parse("2020-06-01") }, { @@ -364,7 +364,7 @@ month: 1, year: 2021, description: "Creating intention for the year using collage boards.", notes: "", tips: "", pub_issue: "VI/1", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2021-01-10"), updated_at: Time.zone.parse("2021-05-01") }, { @@ -374,7 +374,7 @@ month: 7, year: 2018, description: "Creating an expressive wheel of emotions.", notes: "", tips: "", pub_issue: "VII/4", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2018-07-07"), updated_at: Time.zone.parse("2019-08-02") }, { @@ -384,7 +384,7 @@ month: 9, year: 2013, description: "Participants decorate boxes to honor memories or transitions.", notes: "", tips: "", pub_issue: "III/7", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2013-09-09"), updated_at: Time.zone.parse("2014-03-12") }, { @@ -394,7 +394,7 @@ month: 10, year: 2011, description: "Small creative cards with affirmations and courage statements.", notes: "", tips: "", pub_issue: "III/5", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2011-10-10"), updated_at: Time.zone.parse("2012-02-10") }, { @@ -404,7 +404,7 @@ month: 3, year: 2016, description: "Painting stones with messages about resilience.", notes: "", tips: "", pub_issue: "VIII/1", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2016-03-12"), updated_at: Time.zone.parse("2017-01-02") }, { @@ -414,7 +414,7 @@ month: 2, year: 2014, description: "Participants draw trees where roots represent history and leaves represent hopes.", notes: "", tips: "", pub_issue: "IX/3", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2014-02-14"), updated_at: Time.zone.parse("2015-06-22") }, { @@ -424,7 +424,7 @@ month: 4, year: 2022, description: "Decorated stones used to tell collaborative stories.", notes: "", tips: "", pub_issue: "X/4", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2022-04-04"), updated_at: Time.zone.parse("2023-01-01") }, { @@ -434,7 +434,7 @@ month: 10, year: 2020, description: "A mourning ritual using paper lanterns to honor losses.", notes: "", tips: "", pub_issue: "VI/9", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2020-10-10"), updated_at: Time.zone.parse("2021-07-07") }, { @@ -444,7 +444,7 @@ month: 6, year: 2015, description: "Folded books where each page guides a different calming breath.", notes: "", tips: "", pub_issue: "VIII/3", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2015-06-06"), updated_at: Time.zone.parse("2016-01-15") }, { @@ -454,7 +454,7 @@ month: 1, year: 2023, description: "Paper tunnels layered with imagery representing goals for the year.", notes: "", tips: "", pub_issue: "XI/1", - misc1: "", misc2: "", inactive: false, searchable: true, + misc1: "", misc2: "", published: true, searchable: true, created_at: Time.zone.parse("2023-01-01"), updated_at: Time.zone.parse("2023-09-01") } ] diff --git a/lib/tasks/story_data.rake b/lib/tasks/story_data.rake index 330dc6f39..46793edbc 100644 --- a/lib/tasks/story_data.rake +++ b/lib/tasks/story_data.rake @@ -15,7 +15,7 @@ namespace :story_data do featured: resource.featured, permission_given: true, project_id: nil, # resources don't store this, add mapping if needed - published: !resource.inactive, + published: !resource.published, spotlighted_facilitator_id: nil, story_idea_id: nil, website_url: resource.url, diff --git a/lib/tasks/tag_deduping.rake b/lib/tasks/tag_deduping.rake index a7f3f9f00..093957b8f 100644 --- a/lib/tasks/tag_deduping.rake +++ b/lib/tasks/tag_deduping.rake @@ -34,7 +34,7 @@ namespace :tags do sorted = sectors.sort_by do |s| [ - s.published ? 0 : 1, # published first + s.published? ? 0 : 1, # published first -(usage_by_sector_id[s.id] || 0), # highest usage s.created_at || Time.current # oldest ] @@ -125,7 +125,7 @@ namespace :tags do sorted = categories.sort_by do |c| [ - c.respond_to?(:published) && c.published ? 0 : 1, + c.respond_to?(:published) && c.published? ? 0 : 1, -(usage_by_category_id[c.id] || 0), c.created_at || Time.current ] diff --git a/spec/factories/categories.rb b/spec/factories/categories.rb index 408116454..69f958e70 100644 --- a/spec/factories/categories.rb +++ b/spec/factories/categories.rb @@ -9,6 +9,10 @@ published { true } end + trait :unpublished do + published { false } + end + trait :category_age_range do association :category_type, factory: :age_range end diff --git a/spec/factories/category_types.rb b/spec/factories/category_types.rb index b7062a5a4..1d3bcf732 100644 --- a/spec/factories/category_types.rb +++ b/spec/factories/category_types.rb @@ -1,13 +1,21 @@ FactoryBot.define do factory :category_type do sequence(:name) { |n| "Category Type Name #{n}" } - published { true } + published { false } trait :age_range do # name { "AgeRange" } sequence(:name) { |n| "AgeRange #{n}" } end + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end + factory :age_range, parent: :category_type do # name { "AgeRange" } sequence(:name) { |n| "AgeRange #{n}" } diff --git a/spec/factories/community_news.rb b/spec/factories/community_news.rb index 23d12c64e..8a4ee363e 100644 --- a/spec/factories/community_news.rb +++ b/spec/factories/community_news.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :community_news do title { "MyString" } - published { true } + published { false } featured { false } reference_url { "" } youtube_url { "" } @@ -13,6 +13,10 @@ published { true } end + trait :unpublished do + published { false } + end + trait :featured do featured { true } end diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 21acf00d1..4ad961192 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -6,10 +6,17 @@ start_date { 12.days.from_now } end_date { 14.days.from_now } registration_close_date { 13.days.from_now } - inactive { false } cost_cents { 1099 } publicly_visible { false } + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end + trait :registration_closed do registration_close_date { 13.days.ago } end diff --git a/spec/factories/faqs.rb b/spec/factories/faqs.rb index a84a4cebd..e43c9ae03 100644 --- a/spec/factories/faqs.rb +++ b/spec/factories/faqs.rb @@ -2,7 +2,15 @@ factory :faq do question { Faker::Lorem.question } answer { Faker::Lorem.paragraph } - inactive { false } + published { false } sequence(:position) { |n| n } + + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end end end diff --git a/spec/factories/quotes.rb b/spec/factories/quotes.rb index eac4e8d5f..78039ea0a 100644 --- a/spec/factories/quotes.rb +++ b/spec/factories/quotes.rb @@ -4,7 +4,15 @@ speaker_name { Faker::Name.name.gsub("'", " ") } age { rand(18..99) } gender { [ 'M', 'F', 'O', nil ].sample } - inactive { false } + published { false } workshop_id { nil } + + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end end end diff --git a/spec/factories/resources.rb b/spec/factories/resources.rb index 140ab3031..c5cd2691c 100644 --- a/spec/factories/resources.rb +++ b/spec/factories/resources.rb @@ -9,5 +9,13 @@ sector = Sector.published.first || create(:sector) # adjust factory as needed resource.sectors << sector end + + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end end end diff --git a/spec/factories/sectors.rb b/spec/factories/sectors.rb index fdc7eb746..1bd00fa4c 100644 --- a/spec/factories/sectors.rb +++ b/spec/factories/sectors.rb @@ -7,6 +7,10 @@ published { true } end + trait :unpublished do + published { false } + end + trait :other do # name { 'Other' } sequence(:name) { |n| "Other #{n}" } diff --git a/spec/factories/stories.rb b/spec/factories/stories.rb index 7d5cac796..a856fc58e 100644 --- a/spec/factories/stories.rb +++ b/spec/factories/stories.rb @@ -7,5 +7,13 @@ rhino_body { "

My Body

" } association :created_by, factory: :user association :updated_by, factory: :user + + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index ad6536867..8e4549489 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -31,7 +31,7 @@ # comment { "MyText" } # notes { "MyText" } # confirmed { false } - # inactive { false } + # published { true } # legacy { false } # legacy_id { 1 } # super_user { false } diff --git a/spec/factories/workshop_variations.rb b/spec/factories/workshop_variations.rb index 5bacd4c84..adeeeed8f 100644 --- a/spec/factories/workshop_variations.rb +++ b/spec/factories/workshop_variations.rb @@ -4,6 +4,14 @@ sequence(:name) { |n| "Variation #{n}" } code { "

Variation details using CKEditor

" } sequence(:position) { |n| n } - inactive { false } + published { false } + + trait :published do + published { true } + end + + trait :unpublished do + published { false } + end end end diff --git a/spec/factories/workshops.rb b/spec/factories/workshops.rb index bee6f0c18..d9c53a4c4 100644 --- a/spec/factories/workshops.rb +++ b/spec/factories/workshops.rb @@ -6,7 +6,7 @@ title { Faker::Lorem.sentence.gsub("error", "eldor") } - inactive { false } + published { false } featured { false } objective { Faker::Lorem.paragraph.gsub("error", "eldor") } materials { Faker::Lorem.paragraph.gsub("error", "eldor") } @@ -34,7 +34,11 @@ end trait :published do - inactive { false } + published { true } + end + + trait :unpublished do + published { false } end end end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index 202783f3e..d8331d83b 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -38,8 +38,8 @@ describe ".published" do it "returns only published categories" do - visible = create(:category, published: true) - hidden = create(:category, published: false) + visible = create(:category, :published) + hidden = create(:category) expect(Category.published).to contain_exactly(visible) end diff --git a/spec/models/category_type_spec.rb b/spec/models/category_type_spec.rb index 4a0d53220..a652e9790 100644 --- a/spec/models/category_type_spec.rb +++ b/spec/models/category_type_spec.rb @@ -18,8 +18,8 @@ end describe 'scopes' do - let!(:published_category_type) { create(:category_type, published: true) } - let!(:unpublished_category_type) { create(:category_type, published: false) } + let!(:published_category_type) { create(:category_type, :published) } + let!(:unpublished_category_type) { create(:category_type) } it '.published returns only published category types' do expect(CategoryType.published).to include(published_category_type) diff --git a/spec/models/concerns/featureable_spec.rb b/spec/models/concerns/featureable_spec.rb new file mode 100644 index 000000000..13cc61ba0 --- /dev/null +++ b/spec/models/concerns/featureable_spec.rb @@ -0,0 +1,120 @@ +require "rails_helper" + +RSpec.describe Featureable, type: :model do + # Use a real model that includes the concern + # Resource is perfect here + + let!(:featured_record) do + create(:resource, :published, featured: true) + end + + let!(:publicly_featured_record) do + create(:resource, :published, publicly_featured: true, publicly_visible: true) + end + + let!(:both_featured_record) do + create(:resource, :published, featured: true, publicly_featured: true, publicly_visible: true) + end + + let!(:internal_record) do + create(:resource, :published) + end + + let!(:unpublished_featured) do + create(:resource, featured: true) + end + + let!(:unpublished_publicly_featured) do + create(:resource, publicly_featured: true, publicly_visible: true) + end + + # ---------------------------------------------------- + + describe ".featured" do + it "returns only published records with featured: true" do + expect(Resource.featured) + .to contain_exactly(featured_record, both_featured_record) + end + + it "does not include publicly_featured-only records" do + expect(Resource.featured).not_to include(publicly_featured_record) + end + + it "does not include non-featured records" do + expect(Resource.featured).not_to include(internal_record) + end + + it "does not include unpublished records" do + expect(Resource.featured).not_to include(unpublished_featured) + end + + it "includes featured records even if not publicly_visible" do + hidden_featured = create(:resource, featured: true, publicly_visible: false, published: true) + expect(Resource.featured).to include(hidden_featured) + end + + it "is chainable" do + expect(Resource.featured.where(id: featured_record.id)) + .to contain_exactly(featured_record) + end + end + + # ---------------------------------------------------- + + describe ".publicly_featured" do + it "returns only published, publicly_visible, publicly_featured records" do + expect(Resource.publicly_featured) + .to contain_exactly(publicly_featured_record, both_featured_record) + end + + it "does not include featured-only records" do + expect(Resource.publicly_featured).not_to include(featured_record) + end + + it "does not include unpublished records" do + expect(Resource.publicly_featured) + .not_to include(unpublished_publicly_featured) + end + + it "does not include publicly_featured records that are not publicly_visible" do + hidden_publicly_featured = create(:resource, + publicly_featured: true, + publicly_visible: false, + published: true + ) + expect(Resource.publicly_featured).not_to include(hidden_publicly_featured) + end + + it "is chainable" do + expect(Resource.publicly_featured.where(id: publicly_featured_record.id)) + .to contain_exactly(publicly_featured_record) + end + + it "does not raise if publicly_visible column exists" do + expect { Resource.publicly_featured.to_a }.not_to raise_error + end + end + + # ---------------------------------------------------- + + describe ".featured_or_publicly_featured" do + it "returns union of featured and publicly_featured" do + expect(Resource.featured_or_publicly_featured) + .to contain_exactly(featured_record, publicly_featured_record, both_featured_record) + end + + it "does not include normal records" do + expect(Resource.featured_or_publicly_featured).not_to include(internal_record) + end + + it "does not include unpublished records" do + expect(Resource.featured_or_publicly_featured) + .not_to include(unpublished_featured, unpublished_publicly_featured) + end + + it "does not return duplicates" do + ids = Resource.featured_or_publicly_featured.pluck(:id) + expect(ids).to eq(ids.uniq) + end + end +end diff --git a/spec/models/concerns/name_filterable_spec.rb b/spec/models/concerns/name_filterable_spec.rb index 54b6facf4..e6084cf98 100644 --- a/spec/models/concerns/name_filterable_spec.rb +++ b/spec/models/concerns/name_filterable_spec.rb @@ -7,8 +7,11 @@ let!(:other) { create(:sector, name: "Adults") } describe ".names" do - it "returns none for blank input" do - expect(Sector.names(nil)).to be_empty + it "returns all when param is not provided" do + expect(Sector.names(nil)).to match_array([ youth, healing, other ]) + end + + it "returns none when param is provided but empty" do expect(Sector.names("")).to be_empty end diff --git a/spec/models/concerns/publishable_spec.rb b/spec/models/concerns/publishable_spec.rb new file mode 100644 index 000000000..108c955a2 --- /dev/null +++ b/spec/models/concerns/publishable_spec.rb @@ -0,0 +1,83 @@ +require "rails_helper" + +RSpec.describe Publishable, type: :model do + # State fixtures + let!(:public_record) do + Faq.create!(question: "Public", answer: "A", published: true, publicly_visible: true) + end + + let!(:public_draft_record) do + Faq.create!(question: "Public Draft", answer: "A", published: false, publicly_visible: true) + end + + let!(:internal_draft_record) do + Faq.create!(question: "Internal Draft", answer: "A", published: false, publicly_visible: false) + end + + let!(:internal_record) do + Faq.create!(question: "Internal", answer: "A", published: true, publicly_visible: false) + end + + # ------------------------------------------------------------------ + + describe ".published" do + it "returns live records (public + internal) when no param" do + expect(Faq.published) + .to contain_exactly(public_record, internal_record) + end + + it "returns live records (public + internal) when param is empty string" do + expect(Faq.published("")) + .to contain_exactly(public_record, internal_record) + end + + it "returns live records (public + internal) when param is blank" do + expect(Faq.published(nil)) + .to contain_exactly(public_record, internal_record) + end + + it "returns live records (public + internal) when param is true" do + expect(Faq.published(true)) + .to contain_exactly(public_record, internal_record) + end + + it "returns live records (public + internal) when param is 'true'" do + expect(Faq.published('true')) + .to contain_exactly(public_record, internal_record) + end + + it "returns drafts when flag is false" do + expect(Faq.published(false)) + .to contain_exactly(public_draft_record, internal_draft_record) + end + + it "returns drafts when flag is 'false'" do + expect(Faq.published("false")) + .to contain_exactly(public_draft_record, internal_draft_record) + end + end + + # ------------------------------------------------------------------ + + describe ".publicly_visible" do + it "returns only records visible to the public AND live" do + expect(Faq.publicly_visible) + .to contain_exactly(public_record) + end + + it "excludes public drafts" do + expect(Faq.publicly_visible) + .not_to include(public_draft_record) + end + + it "excludes internal live records" do + expect(Faq.publicly_visible) + .not_to include(internal_record) + end + + it "excludes internal drafts" do + expect(Faq.publicly_visible) + .not_to include(internal_draft_record) + end + end +end diff --git a/spec/models/concerns/tag_filterable_spec.rb b/spec/models/concerns/tag_filterable_spec.rb index 15437f00a..ace1ed3ef 100644 --- a/spec/models/concerns/tag_filterable_spec.rb +++ b/spec/models/concerns/tag_filterable_spec.rb @@ -10,23 +10,23 @@ before do create(:sectorable_item, sector: sector_youth, sectorable: workshop_1) + create(:sectorable_item, sector: sector_adult, sectorable: workshop_1) create(:sectorable_item, sector: sector_adult, sectorable: workshop_2) end describe ".tag_names" do - it "returns all records when names are blank" do - expect(Workshop.tag_names(:sectors, nil)) - .to match_array([ workshop_1, workshop_2 ]) + it "returns none when names are blank" do + expect(Workshop.tag_names(:sectors, nil)).to be_empty end it "filters by a single tag name" do - result = Workshop.tag_names(:sectors, "youth") - expect(result).to eq([ workshop_1 ]) + result = Workshop.tag_names(:sectors, "adult") + expect(result).to eq([ workshop_1, workshop_2 ]) end - it "supports multiple tag names" do + it "supports multiple tag names with AND logic" do result = Workshop.tag_names(:sectors, "youth--adult") - expect(result).to match_array([ workshop_1, workshop_2 ]) + expect(result).to match_array([ workshop_1 ]) end it "returns distinct records" do diff --git a/spec/models/faq_spec.rb b/spec/models/faq_spec.rb index 9ff8ef25d..019d6f9c6 100644 --- a/spec/models/faq_spec.rb +++ b/spec/models/faq_spec.rb @@ -14,13 +14,13 @@ end describe "scopes" do - describe ".active" do - let!(:active_faq) { create(:faq, inactive: false) } - let!(:inactive_faq) { create(:faq, inactive: true) } + describe ".published" do + let!(:published_faq) { create(:faq, :published) } + let!(:unpublished_faq) { create(:faq) } - it "returns only active FAQs" do - expect(Faq.active).to contain_exactly(active_faq) - expect(Faq.active).not_to include(inactive_faq) + it "returns only published FAQs" do + expect(Faq.published).to contain_exactly(published_faq) + expect(Faq.published).not_to include(unpublished_faq) end end @@ -36,32 +36,32 @@ end describe ".search_by_params" do - let!(:active_faq) { create(:faq, question: "How to reset password?", inactive: false) } - let!(:inactive_faq) { create(:faq, question: "Admin only FAQ", inactive: true) } + let!(:published_faq) { create(:faq, question: "How to reset password?") } + let!(:unpublished_faq) { create(:faq, :published, question: "Admin only FAQ") } it "returns all when no params" do - expect(Faq.search_by_params({})).to match_array([ active_faq, inactive_faq ]) + expect(Faq.search_by_params({})).to match_array([ published_faq, unpublished_faq ]) end it "filters by query (case-insensitive substring)" do results = Faq.search_by_params({ query: "reset" }) - expect(results).to include(active_faq) - expect(results).not_to include(inactive_faq) + expect(results).to include(published_faq) + expect(results).not_to include(unpublished_faq) end - it "filters by inactive param when true" do - results = Faq.search_by_params({ inactive: true }) - expect(results).to contain_exactly(inactive_faq) + it "filters by unpublished param when true" do + results = Faq.search_by_params({ published: true }) + expect(results).to contain_exactly(unpublished_faq) end - it "filters by inactive param when false" do - results = Faq.search_by_params({ inactive: false }) - expect(results).to contain_exactly(active_faq) + it "filters by unpublished param when false" do + results = Faq.search_by_params({ published: false }) + expect(results).to contain_exactly(published_faq) end - it "chains query and inactive filters" do - results = Faq.search_by_params({ query: "Admin", inactive: true }) - expect(results).to contain_exactly(inactive_faq) + it "chains query and unpublished filters" do + results = Faq.search_by_params({ query: "Admin", published: true }) + expect(results).to contain_exactly(unpublished_faq) end end end diff --git a/spec/models/quote_spec.rb b/spec/models/quote_spec.rb index ccddda765..aebba119f 100644 --- a/spec/models/quote_spec.rb +++ b/spec/models/quote_spec.rb @@ -17,12 +17,12 @@ end describe 'scopes' do - let!(:active_quote) { create(:quote, inactive: false) } - let!(:inactive_quote) { create(:quote, inactive: true) } + let!(:published_quote) { create(:quote, :published) } + let!(:unpublished_quote) { create(:quote, :unpublished) } it '.active returns only active quotes' do - expect(Quote.active).to include(active_quote) - expect(Quote.active).not_to include(inactive_quote) + expect(Quote.published).to include(published_quote) + expect(Quote.published).not_to include(unpublished_quote) end end diff --git a/spec/policies/category_policy_spec.rb b/spec/policies/category_policy_spec.rb index be0c87344..2669a6c97 100644 --- a/spec/policies/category_policy_spec.rb +++ b/spec/policies/category_policy_spec.rb @@ -24,7 +24,7 @@ def policy_for(record: nil, user:) context "with no user" do subject { policy_for(user: nil) } - it { is_expected.not_to be_allowed_to(:tags_index?) } + it { is_expected.to be_allowed_to(:tags_index?) } end end end diff --git a/spec/policies/event_policy_spec.rb b/spec/policies/event_policy_spec.rb index 90955c653..a39e576b8 100644 --- a/spec/policies/event_policy_spec.rb +++ b/spec/policies/event_policy_spec.rb @@ -1,13 +1,13 @@ require "rails_helper" RSpec.describe EventPolicy, type: :policy do - let(:admin_user) { build_stubbed :user, super_user: true } - let(:regular_user) { build_stubbed :user, super_user: false } - let(:published_event) { build_stubbed :event, inactive: false } - let(:public_event) { build_stubbed :event, inactive: false, publicly_visible: true } - let(:unpublished_event) { build_stubbed :event, inactive: true } - let(:open_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.from_now } - let(:closed_registration_event) { build_stubbed :event, inactive: false, registration_close_date: 1.day.ago } + let(:admin_user) { build_stubbed :user, :admin } + let(:regular_user) { build_stubbed :user } + let(:published_event) { build_stubbed :event, :published } + let(:public_event) { build_stubbed :event, publicly_visible: true } + let(:unpublished_event) { build_stubbed :event, :unpublished } + let(:open_registration_event) { build_stubbed :event, registration_close_date: 1.day.from_now } + let(:closed_registration_event) { build_stubbed :event, registration_close_date: 1.day.ago } def policy_for(record: nil, user:) described_class.new(record, user: user) @@ -144,7 +144,7 @@ def policy_for(record: nil, user:) it "returns only visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`inactive` = FALSE') + expect(scope.to_sql).to include('`events`.`published` = TRUE') expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') expect(scope.to_sql).to include('LEFT OUTER JOIN `event_registrations`') end @@ -155,7 +155,7 @@ def policy_for(record: nil, user:) it "returns only visible events with open registration" do scope = policy.apply_scope(Event.all, type: :active_record_relation) - expect(scope.to_sql).to include('`events`.`inactive` = FALSE') + expect(scope.to_sql).to include('`events`.`published` = TRUE') expect(scope.to_sql).to include('registration_close_date IS NULL OR registration_close_date >=') expect(scope.to_sql).not_to include('LEFT OUTER JOIN `registrants`') end diff --git a/spec/policies/resource_policy_spec.rb b/spec/policies/resource_policy_spec.rb index d8add121b..032d79de8 100644 --- a/spec/policies/resource_policy_spec.rb +++ b/spec/policies/resource_policy_spec.rb @@ -3,8 +3,19 @@ RSpec.describe ResourcePolicy, type: :policy do let(:admin_user) { build_stubbed :user, super_user: true } let(:regular_user) { build_stubbed :user, super_user: false } - let(:published_resource) { build_stubbed :resource, inactive: false, kind: "Handout" } - let(:unpublished_resource) { build_stubbed :resource, inactive: true, kind: "Handout" } + let(:internally_published_resource) do + build_stubbed :resource, + kind: Resource::PUBLISHED_KINDS.sample, + published: true, + publicly_visible: false + end + + let(:unpublished_resource) do + build_stubbed :resource, + kind: Resource::PUBLISHED_KINDS.sample, + published: false, + publicly_visible: false + end def policy_for(record: nil, user:) described_class.new(record, user: user) @@ -27,13 +38,13 @@ def policy_for(record: nil, user:) describe "#show?" do context "when resource is published" do context "with admin user" do - subject { policy_for(record: published_resource, user: admin_user) } + subject { policy_for(record: internally_published_resource, user: admin_user) } it { is_expected.to be_allowed_to(:show?) } end context "with regular user" do - subject { policy_for(record: published_resource, user: regular_user) } + subject { policy_for(record: internally_published_resource, user: regular_user) } it { is_expected.to be_allowed_to(:show?) } end @@ -104,13 +115,13 @@ def policy_for(record: nil, user:) describe "#download?" do context "with admin user" do - subject { policy_for(record: published_resource, user: admin_user) } + subject { policy_for(record: internally_published_resource, user: admin_user) } it { is_expected.to be_allowed_to(:download?) } end context "with regular user" do - subject { policy_for(record: published_resource, user: regular_user) } + subject { policy_for(record: internally_published_resource, user: regular_user) } it { is_expected.to be_allowed_to(:download?) } end @@ -145,7 +156,7 @@ def policy_for(record: nil, user:) it "returns only published resources" do scope = policy.apply_scope(Resource.all, type: :active_record_relation) - expect(scope.to_sql).to include('`resources`.`inactive` = FALSE') + expect(scope.to_sql).to include('`resources`.`published` = TRUE') end end end diff --git a/spec/policies/sector_policy_spec.rb b/spec/policies/sector_policy_spec.rb index ccc9f3a40..d0d0ed524 100644 --- a/spec/policies/sector_policy_spec.rb +++ b/spec/policies/sector_policy_spec.rb @@ -24,7 +24,7 @@ def policy_for(record: nil, user:) context "with no user" do subject { policy_for(user: nil) } - it { is_expected.not_to be_allowed_to(:tags_index?) } + it { is_expected.to be_allowed_to(:tags_index?) } end end end diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index 5a16a19bf..20b01a497 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -8,7 +8,7 @@ "start_date": 1.day.from_now, "end_date": 2.days.from_now, "registration_close_date": 3.days.ago, - "inactive": false + "published": true } } @@ -107,7 +107,7 @@ start_date: 1.day.from_now, end_date: 2.days.from_now, registration_close_date: 3.days.ago, - inactive: false + published: true } } follow_redirect! # flash shows after redirect diff --git a/spec/requests/faqs_spec.rb b/spec/requests/faqs_spec.rb index b8319f00a..0f71359ab 100644 --- a/spec/requests/faqs_spec.rb +++ b/spec/requests/faqs_spec.rb @@ -5,7 +5,7 @@ { question: "What is AWBW?", answer: "A Window Between Worlds (AWBW) uses art to heal trauma.", - inactive: false + published: true } end @@ -13,14 +13,15 @@ { question: "", answer: "", - inactive: nil + unpublished: nil } end - let(:admin) { create(:user, :admin) } - let(:regular_user) { create(:user) } - let!(:active_faq) { create(:faq, question: "Public FAQ", answer: "Public FAQ Body", inactive: false) } - let!(:inactive_faq) { create(:faq, question: "Hidden FAQ", answer: "Hidden FAQ Body", inactive: true) } + let(:admin) { create(:user, :admin) } + let(:regular_user) { create(:user) } + let!(:published_faq) { create(:faq, :published, question: "Published FAQ", answer: "Published FAQ Body") } + let!(:unpublished_faq) { create(:faq, question: "Unpublished FAQ", answer: "Unpublished FAQ Body") } + let!(:public_faq) { create(:faq, :published, question: "Public FAQ", answer: "Public FAQ Body", publicly_visible: true) } describe "GET /index" do context "as an admin" do @@ -33,22 +34,33 @@ it "shows all FAQs in the body" do get faqs_path - expect(response.body).to include("Public FAQ") - expect(response.body).to include("Hidden FAQ") + expect(response.body).to include("Published FAQ") + expect(response.body).to include("Unpublished FAQ") + end + + it "filters by unpublished param" do + get faqs_path, params: { published: "false" } # needs to be string param + expect(response.body).to include("Unpublished FAQ") + expect(response.body).not_to match(/>Published FAQ