From 147be9571ad4a5780ea27f33f768965384a4e289 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:55:06 +0000 Subject: [PATCH 01/11] Add story_populations and categorizable associations to Story and StoryIdea models Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- app/controllers/stories_controller.rb | 5 +- app/controllers/story_ideas_controller.rb | 6 ++- app/models/story.rb | 1 + app/models/story_idea.rb | 4 ++ app/views/story_ideas/_form.html.erb | 19 +++++++ ...005210_add_story_populations_to_stories.rb | 5 ++ ...11_add_story_populations_to_story_ideas.rb | 5 ++ ..._add_story_category_type_and_categories.rb | 49 +++++++++++++++++++ 8 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20260120005210_add_story_populations_to_stories.rb create mode 100644 db/migrate/20260120005211_add_story_populations_to_story_ideas.rb create mode 100644 db/migrate/20260120005212_add_story_category_type_and_categories.rb diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 0cdaa2dd6..deca1854a 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -163,10 +163,13 @@ def story_params :title, :rhino_body, :featured, :published, :publicly_visible, :publicly_featured, :youtube_url, :website_url, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, :created_by_id, :updated_by_id, :story_idea_id, :spotlighted_facilitator_id, + story_populations: [], category_ids: [], sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], - gallery_assets_attributes: [ :id, :file, :_destroy ] + gallery_assets_attributes: [ :id, :file, :_destroy ], + categorizable_items_attributes: [ :id, :category_id, :_destroy ], + category_ids: [] ) end diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb index 3e61c49e1..fa67a7167 100644 --- a/app/controllers/story_ideas_controller.rb +++ b/app/controllers/story_ideas_controller.rb @@ -107,8 +107,12 @@ def story_idea_params :title, :body, :youtube_url, :permission_given, :publish_preferences, :promoted_to_story, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, + :created_by_id, :updated_by_id, + story_populations: [], primary_asset_attributes: [ :id, :file, :_destroy ], - gallery_assets_attributes: [ :id, :file, :_destroy ] + gallery_assets_attributes: [ :id, :file, :_destroy ], + categorizable_items_attributes: [ :id, :category_id, :_destroy ], + category_ids: [] ) end end diff --git a/app/models/story.rb b/app/models/story.rb index 2b20225ee..9353fcf4e 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -37,6 +37,7 @@ class Story < ApplicationRecord # Nested attributes 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 + accepts_nested_attributes_for :categorizable_items, allow_destroy: true, reject_if: :all_blank # SearchCop include SearchCop diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb index 4b57f7347..3ae5c61ec 100644 --- a/app/models/story_idea.rb +++ b/app/models/story_idea.rb @@ -22,6 +22,7 @@ def self.search_by_params(params) belongs_to :windows_type belongs_to :workshop, optional: true has_many :bookmarks, as: :bookmarkable, dependent: :destroy + has_many :categorizable_items, dependent: :destroy, inverse_of: :categorizable, as: :categorizable has_many :notifications, as: :noticeable, dependent: :destroy has_many :stories # Asset associations @@ -30,6 +31,8 @@ def self.search_by_params(params) has_many :gallery_assets, -> { where(type: "GalleryAsset") }, as: :owner, class_name: "GalleryAsset", dependent: :destroy has_many :assets, as: :owner, dependent: :destroy + # has_many through + has_many :categories, through: :categorizable_items # Validations validates :created_by_id, presence: true @@ -43,6 +46,7 @@ def self.search_by_params(params) # Nested attributes 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 + accepts_nested_attributes_for :categorizable_items, allow_destroy: true, reject_if: :all_blank def name "StoryIdea ##{id}" diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb index 64dc5f9c9..1876aafd7 100644 --- a/app/views/story_ideas/_form.html.erb +++ b/app/views/story_ideas/_form.html.erb @@ -87,6 +87,25 @@ input_html: { rows: 10, class: ("readonly" if promoted_to_story) } %> + +
+ +
+ <% ["Adults", "Children", "Colleagues", "Community", "Families", "Self", "Teens"].each do |population| %> +
+ <%= check_box_tag "story_idea[story_populations][]", population, + (f.object.story_populations || []).include?(population), + id: "story_idea_story_populations_#{population.downcase}", + disabled: promoted_to_story, + class: "rounded border-gray-300 text-purple-600 shadow-sm focus:border-purple-300 focus:ring focus:ring-purple-200 focus:ring-opacity-50" %> + <%= label_tag "story_idea_story_populations_#{population.downcase}", population, class: "ml-2 text-sm text-gray-700" %> +
+ <% end %> +
+
+
<%= f.input :publish_preferences, as: :select, diff --git a/db/migrate/20260120005210_add_story_populations_to_stories.rb b/db/migrate/20260120005210_add_story_populations_to_stories.rb new file mode 100644 index 000000000..466e52c3d --- /dev/null +++ b/db/migrate/20260120005210_add_story_populations_to_stories.rb @@ -0,0 +1,5 @@ +class AddStoryPopulationsToStories < ActiveRecord::Migration[8.1] + def change + add_column :stories, :story_populations, :json + end +end diff --git a/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb b/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb new file mode 100644 index 000000000..5d0c0a75b --- /dev/null +++ b/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb @@ -0,0 +1,5 @@ +class AddStoryPopulationsToStoryIdeas < ActiveRecord::Migration[8.1] + def change + add_column :story_ideas, :story_populations, :json + end +end diff --git a/db/migrate/20260120005212_add_story_category_type_and_categories.rb b/db/migrate/20260120005212_add_story_category_type_and_categories.rb new file mode 100644 index 000000000..ac5648f8a --- /dev/null +++ b/db/migrate/20260120005212_add_story_category_type_and_categories.rb @@ -0,0 +1,49 @@ +class AddStoryCategoryTypeAndCategories < ActiveRecord::Migration[8.1] + def up + # Create the StoryCategory type if it doesn't exist + story_category_type = CategoryType.find_or_create_by!(name: "StoryCategory") + + # List of story categories from the WordPress form + story_categories = [ + "Advocacy (legislation, voter mobilization, etc.)", + "Batterers Intervention (working with people who have caused harm)", + "Child Abuse / Neglect", + "Community Engagement", + "Community Violence (gang violence, police violence, mass shootings etc.)", + "Court & Legal System (including law enforcement and probation)", + "Disability Services", + "Domestic Violence", + "Donor Engagement", + "Foster Care", + "Grief & Loss", + "Homelessness", + "Human Trafficking", + "Immigration (family separation, deportation, refugees/asylees, etc.)", + "Incarceration", + "LGBTQIA+", + "Mental Health (DSM, suicidal ideation, etc.)", + "Military & Veterans", + "Physical & Terminal Illness", + "Schools & Universities", + "Self-Care & Personal Growth", + "Sexual Assault", + "Social Justice (anti-oppression, restorative justice, systems change, etc.)", + "Staff & Organizational Development", + "Substance Abuse Recovery" + ] + + # Create each category with proper ordering + story_categories.each_with_index do |category_name, index| + story_category_type.categories.find_or_create_by!(name: category_name) do |category| + category.position = (index + 1) * 10 + category.published = true + end + end + end + + def down + story_category_type = CategoryType.find_by(name: "StoryCategory") + story_category_type&.categories&.destroy_all + story_category_type&.destroy + end +end From 6a51ad0403e592239ca4c0574c3148548c6f9bdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:57:30 +0000 Subject: [PATCH 02/11] Add story_categories scope to Category model and update controllers Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com> --- app/models/category.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/category.rb b/app/models/category.rb index 811f620e5..80457c213 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -10,6 +10,7 @@ class Category < ApplicationRecord # Scopes # See NameFilterable, Publishable scope :age_ranges, -> { joins(:category_type).where(category_types: { name: "AgeRange" }) } + scope :story_categories, -> { joins(:category_type).where(category_types: { name: "StoryCategory" }) } scope :ordered_by_position_and_name, -> { reorder(position: :asc, name: :asc) } # Validations From df2e1ce310b4d41318ccb2104b837bf861ecef57 Mon Sep 17 00:00:00 2001 From: maebeale Date: Sun, 15 Feb 2026 09:48:37 -0500 Subject: [PATCH 03/11] WIP: story updates --- app/controllers/stories_controller.rb | 1 - app/controllers/story_ideas_controller.rb | 1 - app/views/story_ideas/_form.html.erb | 24 ++------- ...005210_add_story_populations_to_stories.rb | 5 -- ...11_add_story_populations_to_story_ideas.rb | 5 -- ..._add_story_category_type_and_categories.rb | 49 ------------------- 6 files changed, 4 insertions(+), 81 deletions(-) delete mode 100644 db/migrate/20260120005210_add_story_populations_to_stories.rb delete mode 100644 db/migrate/20260120005211_add_story_populations_to_story_ideas.rb delete mode 100644 db/migrate/20260120005212_add_story_category_type_and_categories.rb diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index deca1854a..dc9db5239 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -163,7 +163,6 @@ def story_params :title, :rhino_body, :featured, :published, :publicly_visible, :publicly_featured, :youtube_url, :website_url, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, :created_by_id, :updated_by_id, :story_idea_id, :spotlighted_facilitator_id, - story_populations: [], category_ids: [], sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb index fa67a7167..de7766966 100644 --- a/app/controllers/story_ideas_controller.rb +++ b/app/controllers/story_ideas_controller.rb @@ -108,7 +108,6 @@ def story_idea_params :permission_given, :publish_preferences, :promoted_to_story, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, :created_by_id, :updated_by_id, - story_populations: [], primary_asset_attributes: [ :id, :file, :_destroy ], gallery_assets_attributes: [ :id, :file, :_destroy ], categorizable_items_attributes: [ :id, :category_id, :_destroy ], diff --git a/app/views/story_ideas/_form.html.erb b/app/views/story_ideas/_form.html.erb index 1876aafd7..8362a0c5b 100644 --- a/app/views/story_ideas/_form.html.erb +++ b/app/views/story_ideas/_form.html.erb @@ -81,31 +81,15 @@
<%= f.input :body, as: :text, - label: "Share details of your story", - hint: "(HTML allowed)", + label: "Please share details about your participant(s) (while maintaining confidentiality) + and their workshop goals, including what happened during the workshop, and how creating made an impact. + Include direct quotes whenever possible.", + hint: "(For more tips on what makes a powerful story, see the story tips PDF above.)", disabled: promoted_to_story, input_html: { rows: 10, class: ("readonly" if promoted_to_story) } %>
-
- -
- <% ["Adults", "Children", "Colleagues", "Community", "Families", "Self", "Teens"].each do |population| %> -
- <%= check_box_tag "story_idea[story_populations][]", population, - (f.object.story_populations || []).include?(population), - id: "story_idea_story_populations_#{population.downcase}", - disabled: promoted_to_story, - class: "rounded border-gray-300 text-purple-600 shadow-sm focus:border-purple-300 focus:ring focus:ring-purple-200 focus:ring-opacity-50" %> - <%= label_tag "story_idea_story_populations_#{population.downcase}", population, class: "ml-2 text-sm text-gray-700" %> -
- <% end %> -
-
-
<%= f.input :publish_preferences, as: :select, diff --git a/db/migrate/20260120005210_add_story_populations_to_stories.rb b/db/migrate/20260120005210_add_story_populations_to_stories.rb deleted file mode 100644 index 466e52c3d..000000000 --- a/db/migrate/20260120005210_add_story_populations_to_stories.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddStoryPopulationsToStories < ActiveRecord::Migration[8.1] - def change - add_column :stories, :story_populations, :json - end -end diff --git a/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb b/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb deleted file mode 100644 index 5d0c0a75b..000000000 --- a/db/migrate/20260120005211_add_story_populations_to_story_ideas.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddStoryPopulationsToStoryIdeas < ActiveRecord::Migration[8.1] - def change - add_column :story_ideas, :story_populations, :json - end -end diff --git a/db/migrate/20260120005212_add_story_category_type_and_categories.rb b/db/migrate/20260120005212_add_story_category_type_and_categories.rb deleted file mode 100644 index ac5648f8a..000000000 --- a/db/migrate/20260120005212_add_story_category_type_and_categories.rb +++ /dev/null @@ -1,49 +0,0 @@ -class AddStoryCategoryTypeAndCategories < ActiveRecord::Migration[8.1] - def up - # Create the StoryCategory type if it doesn't exist - story_category_type = CategoryType.find_or_create_by!(name: "StoryCategory") - - # List of story categories from the WordPress form - story_categories = [ - "Advocacy (legislation, voter mobilization, etc.)", - "Batterers Intervention (working with people who have caused harm)", - "Child Abuse / Neglect", - "Community Engagement", - "Community Violence (gang violence, police violence, mass shootings etc.)", - "Court & Legal System (including law enforcement and probation)", - "Disability Services", - "Domestic Violence", - "Donor Engagement", - "Foster Care", - "Grief & Loss", - "Homelessness", - "Human Trafficking", - "Immigration (family separation, deportation, refugees/asylees, etc.)", - "Incarceration", - "LGBTQIA+", - "Mental Health (DSM, suicidal ideation, etc.)", - "Military & Veterans", - "Physical & Terminal Illness", - "Schools & Universities", - "Self-Care & Personal Growth", - "Sexual Assault", - "Social Justice (anti-oppression, restorative justice, systems change, etc.)", - "Staff & Organizational Development", - "Substance Abuse Recovery" - ] - - # Create each category with proper ordering - story_categories.each_with_index do |category_name, index| - story_category_type.categories.find_or_create_by!(name: category_name) do |category| - category.position = (index + 1) * 10 - category.published = true - end - end - end - - def down - story_category_type = CategoryType.find_by(name: "StoryCategory") - story_category_type&.categories&.destroy_all - story_category_type&.destroy - end -end From e77035fb63c975a07df717b5a879146898203cd4 Mon Sep 17 00:00:00 2001 From: maebeale Date: Sun, 15 Feb 2026 11:57:30 -0500 Subject: [PATCH 04/11] Add story_specific as category_type boolean and use it only on stories and not other places tags show --- app/controllers/community_news_controller.rb | 2 +- app/controllers/events_controller.rb | 2 +- app/controllers/resources_controller.rb | 2 +- app/controllers/stories_controller.rb | 2 +- app/controllers/workshop_ideas_controller.rb | 2 +- app/controllers/workshops_controller.rb | 2 +- app/models/sector.rb | 2 ++ ...add_story_specific_and_display_text_to_category_types.rb | 6 ++++++ 8 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20260215000000_add_story_specific_and_display_text_to_category_types.rb diff --git a/app/controllers/community_news_controller.rb b/app/controllers/community_news_controller.rb index e9ea567b0..3b0a922a4 100644 --- a/app/controllers/community_news_controller.rb +++ b/app/controllers/community_news_controller.rb @@ -117,7 +117,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) @community_news.build_primary_asset if @community_news.primary_asset.blank? diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index f46054aaf..2ac026004 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -107,7 +107,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) end diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index 8c9da4b8f..2b42dfce5 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -159,7 +159,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index dc9db5239..aead5b073 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -135,7 +135,7 @@ def set_form_variables .order(:position, :name) .group_by(&:category_type) .select { |type, _| type.nil? || type.published? } - .sort_by { |type, _| type&.name.to_s.downcase } + .sort_by { |type, _| [type&.story_specific? ? 0 : 1, type&.name.to_s.downcase] } @sectors = Sector.published.order(:name) @story.build_primary_asset if @story.primary_asset.blank? @story.gallery_assets.build diff --git a/app/controllers/workshop_ideas_controller.rb b/app/controllers/workshop_ideas_controller.rb index 6d4435c86..86f8de0ae 100644 --- a/app/controllers/workshop_ideas_controller.rb +++ b/app/controllers/workshop_ideas_controller.rb @@ -79,7 +79,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @workshop_idea.build_primary_asset if @workshop_idea.primary_asset.blank? @workshop_idea.gallery_assets.build diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 01fbcf61a..25efd5af9 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -201,7 +201,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) diff --git a/app/models/sector.rb b/app/models/sector.rb index 968042fee..e1323d903 100644 --- a/app/models/sector.rb +++ b/app/models/sector.rb @@ -24,6 +24,8 @@ class Sector < ApplicationRecord "Other" ] + STORY_DISPLAY_TEXT = "Which sectors apply?" + has_many :sectorable_items, dependent: :destroy has_many :workshops, through: :sectorable_items, source: :sectorable, source_type: "Workshop" diff --git a/db/migrate/20260215000000_add_story_specific_and_display_text_to_category_types.rb b/db/migrate/20260215000000_add_story_specific_and_display_text_to_category_types.rb new file mode 100644 index 000000000..b515a2a4c --- /dev/null +++ b/db/migrate/20260215000000_add_story_specific_and_display_text_to_category_types.rb @@ -0,0 +1,6 @@ +class AddStorySpecificAndDisplayTextToCategoryTypes < ActiveRecord::Migration[8.1] + def change + add_column :category_types, :story_specific, :boolean, default: false + add_column :category_types, :display_text, :string + end +end From bab3886a06c3dd6aa7217f49426fecacf15914c8 Mon Sep 17 00:00:00 2001 From: maebeale Date: Sun, 15 Feb 2026 11:59:22 -0500 Subject: [PATCH 05/11] Story and StoryIdea display and promotion process wip --- app/controllers/stories_controller.rb | 19 ++- app/controllers/story_ideas_controller.rb | 84 ++++++++-- app/helpers/title_display_helper.rb | 10 ++ app/models/category_type.rb | 6 + app/models/story.rb | 2 +- app/models/story_idea.rb | 3 +- app/views/stories/_form.html.erb | 103 ++++++++---- app/views/story_ideas/_form.html.erb | 193 +++++++++++++++++++--- app/views/story_ideas/edit.html.erb | 5 - 9 files changed, 346 insertions(+), 79 deletions(-) diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index aead5b073..09cad9a4d 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -57,7 +57,7 @@ def edit end def create - @story = Story.new(story_params) + @story = Story.new(story_params.except(:category_ids, :sector_ids)) authorize! @story success = false @@ -91,8 +91,11 @@ def update success = false Story.transaction do - if @story.update(story_params.except(:images)) + if @story.update(story_params.except(:images, :category_ids, :sector_ids)) assign_associations(@story) + if params[:promote_idea_assets] == "true" + @story.attach_assets_from_idea! + end success = true end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e @@ -137,6 +140,15 @@ def set_form_variables .select { |type, _| type.nil? || type.published? } .sort_by { |type, _| [type&.story_specific? ? 0 : 1, type&.name.to_s.downcase] } @sectors = Sector.published.order(:name) + submitted_sector_ids = Array(params.dig(:story, :sector_ids)).reject(&:blank?) + submitted_category_ids = Array(params.dig(:story, :category_ids)).reject(&:blank?) + if submitted_sector_ids.any? || submitted_category_ids.any? + @preselected_sector_ids = submitted_sector_ids.map(&:to_i) + @preselected_category_ids = submitted_category_ids.map(&:to_i) + elsif @story_idea + @preselected_sector_ids = @story_idea.sector_ids + @preselected_category_ids = @story_idea.category_ids + end @story.build_primary_asset if @story.primary_asset.blank? @story.gallery_assets.build end @@ -167,13 +179,12 @@ def story_params sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], gallery_assets_attributes: [ :id, :file, :_destroy ], - categorizable_items_attributes: [ :id, :category_id, :_destroy ], - category_ids: [] ) end def set_story_attributes_from(idea) { + story_idea_id: idea.id, rhino_body: idea.body, organization_id: idea.organization.id, workshop_id: idea.workshop_id, diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb index de7766966..cbbd1cc7b 100644 --- a/app/controllers/story_ideas_controller.rb +++ b/app/controllers/story_ideas_controller.rb @@ -31,19 +31,30 @@ def edit end def create - @story_idea = StoryIdea.new(story_idea_params) + @story_idea = StoryIdea.new(story_idea_params.except(:category_ids, :sector_ids)) @story_idea.created_by = current_user @story_idea.updated_by = current_user authorize! @story_idea - if @story_idea.save - NotificationServices::CreateNotification.call( - noticeable: @story_idea, - kind: :idea_submitted_fyi, - recipient_role: :admin, - recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"), - notification_type: 0) + success = false + + StoryIdea.transaction do + if @story_idea.save + assign_associations(@story_idea) + NotificationServices::CreateNotification.call( + noticeable: @story_idea, + kind: :idea_submitted_fyi, + recipient_role: :admin, + recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"), + notification_type: 0) + success = true + end + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e + Rails.logger.error "StoryIdea create failed: #{e.class} - #{e.message}" + raise ActiveRecord::Rollback + end + if success flash[:notice] = "StoryIdea was successfully created." if allowed_to?(:index?, StoryIdea) redirect_to story_ideas_path @@ -60,7 +71,19 @@ def update @story_idea.updated_by = current_user authorize! @story_idea - if @story_idea.update(story_idea_params.except(:images)) + success = false + + StoryIdea.transaction do + if @story_idea.update(story_idea_params.except(:images, :category_ids, :sector_ids)) + assign_associations(@story_idea) + success = true + end + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e + Rails.logger.error "StoryIdea update failed: #{e.class} - #{e.message}" + raise ActiveRecord::Rollback + end + + if success flash[:notice] = "StoryIdea was successfully updated." if allowed_to?(:index?, StoryIdea) redirect_to story_ideas_path, status: :see_other @@ -89,13 +112,44 @@ def set_form_variables @workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title) - @users = authorized_scope(User.has_access.includes(:person)) - @users = @users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id - @users = @users.includes(:person).distinct.order("people.first_name, people.last_name") + users = authorized_scope(User.has_access.includes(:person)) + users = users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id + @users = users.distinct.order("people.first_name, people.last_name") + + @story_population_type = CategoryType.find_by(name: "StoryPopulation") + @story_population_categories = @story_population_type&.categories&.published&.order(:name) || [] + @sectors = Sector.published.order(:name) + submitted_sector_ids = Array(params.dig(:story_idea, :sector_ids)).reject(&:blank?) + submitted_category_ids = Array(params.dig(:story_idea, :category_ids)).reject(&:blank?) + if submitted_sector_ids.any? || submitted_category_ids.any? + @preselected_sector_ids = submitted_sector_ids.map(&:to_i) + @preselected_category_ids = submitted_category_ids.map(&:to_i) + end + + if @story_idea.persisted? + @categories_grouped = + Category + .includes(:category_type) + .published + .order(:position, :name) + .group_by(&:category_type) + .select { |type, _| type.nil? || type.published? } + .sort_by { |type, _| [type&.story_specific? ? 0 : 1, type&.name.to_s.downcase] } + end +>>>>>>> ac3201a15 (Story and StoryIdea display and promotion process wip) @story_idea.build_primary_asset if @story_idea.primary_asset.blank? @story_idea.gallery_assets.build end + def assign_associations(story_idea) + selected_category_ids = Array(params[:story_idea][:category_ids]).reject(&:blank?).map(&:to_i) + story_idea.categories = Category.where(id: selected_category_ids) + + selected_sector_ids = Array(params[:story_idea][:sector_ids]).reject(&:blank?).map(&:to_i) + story_idea.sectors = Sector.where(id: selected_sector_ids) + story_idea.save! + end + private def set_story_idea @@ -108,10 +162,10 @@ def story_idea_params :permission_given, :publish_preferences, :promoted_to_story, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, :created_by_id, :updated_by_id, + category_ids: [], + sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], - gallery_assets_attributes: [ :id, :file, :_destroy ], - categorizable_items_attributes: [ :id, :category_id, :_destroy ], - category_ids: [] + gallery_assets_attributes: [ :id, :file, :_destroy ] ) end end diff --git a/app/helpers/title_display_helper.rb b/app/helpers/title_display_helper.rb index 21249f4db..a454844db 100644 --- a/app/helpers/title_display_helper.rb +++ b/app/helpers/title_display_helper.rb @@ -23,6 +23,16 @@ def title_with_badges(record, font_size: "text-lg", record_title: nil, ) end + # --- Promoted from story idea badge --- + if record.respond_to?(:story_idea) && record.story_idea.present? + fragments << content_tag( + :span, + content_tag(:i, "", class: "fa-solid fa-arrow-up-from-bracket mr-1") + " Promoted", + class: "inline-flex items-center px-2 py-0.5 rounded-full + text-sm font-medium bg-green-100 text-green-800 whitespace-nowrap" + ) + end + title_content = record_title || record.title.to_s if display_windows_type && record.respond_to?(:windows_type) && record.windows_type.present? diff --git a/app/models/category_type.rb b/app/models/category_type.rb index 4a8abd1c9..0952f338d 100644 --- a/app/models/category_type.rb +++ b/app/models/category_type.rb @@ -9,4 +9,10 @@ class CategoryType < ApplicationRecord # Scopes # See Publishable + scope :general, -> { where(story_specific: false) } + scope :story_specific, -> { where(story_specific: true) } + + def display_label + display_text.presence || name.titleize + end end diff --git a/app/models/story.rb b/app/models/story.rb index 9353fcf4e..5dffdf2c8 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -37,7 +37,6 @@ class Story < ApplicationRecord # Nested attributes 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 - accepts_nested_attributes_for :categorizable_items, allow_destroy: true, reject_if: :all_blank # SearchCop include SearchCop @@ -94,6 +93,7 @@ def sector_names_all def attach_assets_from_idea! return unless story_idea + assets.destroy_all story_idea.assets.find_each do |asset| new_asset = assets.build(type: asset.type) new_asset.file.attach(asset.file.blob) diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb index 3ae5c61ec..c0124a2be 100644 --- a/app/models/story_idea.rb +++ b/app/models/story_idea.rb @@ -23,6 +23,7 @@ def self.search_by_params(params) belongs_to :workshop, optional: true has_many :bookmarks, as: :bookmarkable, dependent: :destroy has_many :categorizable_items, dependent: :destroy, inverse_of: :categorizable, as: :categorizable + has_many :sectorable_items, dependent: :destroy, inverse_of: :sectorable, as: :sectorable has_many :notifications, as: :noticeable, dependent: :destroy has_many :stories # Asset associations @@ -33,6 +34,7 @@ def self.search_by_params(params) has_many :assets, as: :owner, dependent: :destroy # has_many through has_many :categories, through: :categorizable_items + has_many :sectors, through: :sectorable_items # Validations validates :created_by_id, presence: true @@ -46,7 +48,6 @@ def self.search_by_params(params) # Nested attributes 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 - accepts_nested_attributes_for :categorizable_items, allow_destroy: true, reject_if: :all_blank def name "StoryIdea ##{id}" diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index fbb6424d5..a74bb577f 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -2,6 +2,18 @@ <%= render 'shared/errors', resource: @story if @story.errors.any? %> <% story_idea = @story_idea || f.object.story_idea %> + <% selected_sector_ids = @preselected_sector_ids || @story.sector_ids %> + <% selected_category_ids = @preselected_category_ids || @story.category_ids %> + + <% if story_idea %> +
+ + + Promoted from + <%= link_to story_idea.full_name, edit_story_idea_path(story_idea), class: "underline hover:text-blue-600" %> + +
+ <% end %>
<%= f.input :title, as: :text, @@ -68,18 +80,23 @@
+ <% story_idea_label = if f.object.story_idea + (link_to "Source story idea", + story_idea_path(f.object.story_idea), class: "hover:underline") + else + "Source story idea" + end %> <%= f.input :story_idea_id, collection: @story_ideas, label_method: :full_name, value_method: :id, selected: f.object.story_idea_id || @story_idea&.id, - label: (f.object.story_idea ? (link_to "Story idea", - story_idea_path(story_idea), class: "hover:underline") : "Story idea").html_safe, + label: story_idea_label.html_safe, prompt: "Select idea" %>
<%= f.input :created_by_id, - collection: @users, label_method: :full_name, + collection: @users, label_method: :full_name_with_email, value_method: :id, selected: f.object.organization_id || @story_idea&.organization_id, label: (f.object.created_by ? ( @@ -92,12 +109,15 @@
Story idea author credit:
- <%= story_idea.author_credit %> -
+ <% if story_idea.created_by.person %> + <%= person_profile_button(story_idea.created_by.person, display_name: story_idea.author_credit, subtitle: story_idea.created_by.person.full_name) %> + <% else %> + <%= story_idea.author_credit %> + <% end %>
Story idea "publish preferences":
-
<%= story_idea.publish_preferences %>
+
<%= link_to story_idea.publish_preferences, edit_story_idea_path(story_idea, anchor: "publish-preferences"), class: "hover:underline hover:text-blue-600" %>
<% end %>
@@ -126,7 +146,45 @@
<% end %>
- <%= render "shared/form_image_fields", f: f, include_primary_asset: true %> + <% if story_idea %> + <% idea_assets = story_idea.assets.select { |a| a.file.attached? } %> + <% if idea_assets.any? %> + <% idea_blob_ids = idea_assets.map { |a| a.file.blob_id }.sort %> + <% story_blob_ids = f.object.assets.select { |a| a.file.attached? }.map { |a| a.file.blob_id }.sort %> + <% assets_already_match = idea_blob_ids == story_blob_ids %> + <% unless f.object.persisted? && assets_already_match %> +
+
+ <%= label_tag :promote_idea_assets, class: "flex items-start space-x-3 cursor-pointer" do %> + <%= check_box_tag :promote_idea_assets, true, f.object.new_record?, class: "mt-1 h-5 w-5 text-blue-600 rounded border-gray-300 focus:ring-blue-500" %> +
+ + Check this box to transfer attachments from the story idea. + +

+ + This will replace any existing attachments on this story once you click submit. +

+
+ <% end %> +
+ +

<%= link_to "Story idea attachments:", edit_story_idea_path(story_idea, anchor: "attachments"), class: "underline hover:text-blue-600" %>

+
+ <% idea_assets.each do |asset| %> +
+ <%= render "assets/display_image", resource: story_idea, item: asset, variant: :gallery %> + <%= asset.type.underscore.humanize %> +
+ <% end %> +
+
+ <% end %> + <% end %> + <% end %> + + <%= render "shared/form_image_fields", f: f, include_primary_asset: true, + primary_title: "Primary attachment", gallery_title: "Additional attachment" %>
@@ -136,13 +194,13 @@ id="tags_button" class=" flex items-center justify-between cursor-pointer w-full - bg-gray-500 text-white hover:bg-gray-300 px-3 py-2 rounded-md" + bg-gray-500 text-white hover:bg-gray-300 px-3 py-2 rounded-t-md" data-action="dropdown#toggle" data-dropdown-payload-param='[{"tags":"hidden"}, {"tags_arrow":"rotate-180"}, {"tags_button":"rounded-md rounded-t-md"}]'> Tags @@ -154,11 +212,11 @@ -