Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ def edit

def create
@story = Story.new(story_params.except(:category_ids, :sector_ids))
# A free-text author name covers facilitators who aren't in the CMS, so an
# author User may not be selected. Fall back to the current admin as creator.
@story.created_by_id ||= current_user.id

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free-text authors may not be in the CMS, so no author User is selected. We default created_by to the current admin here to satisfy the required association while author_name carries the display credit.

authorize! @story

success = false
Expand Down Expand Up @@ -131,7 +134,7 @@ def set_form_variables
.references(:users)
.order(:created_at)
@people = Person.order(Arel.sql("LOWER(first_name), LOWER(last_name)"))
@users = User.has_access.includes(:person).left_joins(:person).order(Arel.sql("people.first_name IS NULL, LOWER(people.first_name), LOWER(people.last_name), LOWER(users.email)"))
@users = User.includes(:person).left_joins(:person).order(Arel.sql("people.first_name IS NULL, LOWER(people.first_name), LOWER(people.last_name), LOWER(users.email)"))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped has_access so inactive/unconfirmed facilitators are selectable, mirroring how the spotlighted-facilitator dropdown lists all people.

@windows_types = WindowsType.all
@workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title)
@categories_grouped =
Expand Down Expand Up @@ -189,7 +192,7 @@ def story_params
params.require(:story).permit(
: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, :author_credit_preference,
:created_by_id, :updated_by_id, :story_idea_id, :spotlighted_facilitator_id, :author_credit_preference, :author_name,
category_ids: [],
sector_ids: [],
primary_asset_attributes: [ :id, :file, :_destroy ],
Expand Down
2 changes: 2 additions & 0 deletions app/models/concerns/author_creditable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module AuthorCreditable
}.freeze

def author_credit
free_text = try(:author_name)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try(:author_name) so the shared concern keeps working for StoryIdea, which has no author_name column. Free text intentionally overrides the credit preference.

return free_text if free_text.present?
person = created_by&.person
Comment on lines 22 to 25
case author_credit_preference
when "full_name" then person&.full_name || "Anonymous"
Expand Down
6 changes: 6 additions & 0 deletions app/views/stories/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@
style: custom_caret_style,
onchange: select_caret_onchange
} %>

<%= f.input :author_name,
as: :string,
label: "Or type an author name",
hint: "Use for facilitators not in the CMS, or where we only have a first name. Overrides the dropdown and credit preference.",
input_html: { class: "w-full px-3 py-2 border border-gray-300 rounded-lg" } %>
</div>

<div class="flex-1 mb-4 md:mb-0">
Expand Down
2 changes: 1 addition & 1 deletion app/views/stories/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<!-- Meta Data (exact screenshot style) -->
<div class="text-sm text-gray-600 space-y-0.5">
<p><strong>Story by:</strong>
<% if @story.created_by.person&.profile_is_searchable %>
<% if @story.author_name.blank? && @story.created_by.person&.profile_is_searchable %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suppress the profile link for free-text authors — otherwise it would link to the admin creator rather than the credited facilitator.

<%= link_to @story.author_credit, person_path(@story.created_by) %>
<% else %>
Comment on lines +31 to 33
<%= @story.author_credit %>
Expand Down
9 changes: 9 additions & 0 deletions db/migrate/20260604120000_add_author_name_to_stories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddAuthorNameToStories < ActiveRecord::Migration[8.1]
def up
add_column :stories, :author_name, :string
end

def down
remove_column :stories, :author_name, if_exists: true
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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_05_29_155200) do
ActiveRecord::Schema[8.1].define(version: 2026_06_04_120000) 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
Expand Down Expand Up @@ -899,6 +899,7 @@

create_table "stories", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.string "author_credit_preference"
t.string "author_name"
t.text "body"
t.datetime "created_at", null: false
t.integer "created_by_id", null: false
Expand Down
15 changes: 15 additions & 0 deletions spec/models/story_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
RSpec.describe Story, type: :model do
it_behaves_like "author_creditable", factory: :story

describe "#author_credit with a free-text author_name" do
let(:author_user) { create(:user, :with_person) }
let(:story) { create(:story, created_by: author_user, author_credit_preference: "full_name") }

it "returns the free-text name, overriding the created_by person and credit preference" do
story.update!(author_name: "Jane (first name only)")
expect(story.author_credit).to eq("Jane (first name only)")
end

it "falls back to the created_by person when author_name is blank" do
story.update!(author_name: "")
expect(story.author_credit).to eq(author_user.person.full_name)
end
end

describe "#attach_assets_from_idea!" do
let(:idea) { create(:story_idea) }
let(:story) { create(:story, story_idea: idea) }
Expand Down
22 changes: 22 additions & 0 deletions spec/requests/stories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,28 @@ def titles_in_response

expect(response).to redirect_to(story_url(Story.last))
end

it "creates a story with a free-text author name and no selected author" do
attributes = base_attributes.merge(author_name: "Unlisted Facilitator")
attributes.delete(:created_by_id)

expect {
post stories_url, params: { story: attributes }
}.to change(Story, :count).by(1)

story = Story.last
expect(story.author_name).to eq("Unlisted Facilitator")
expect(story.created_by).to eq(admin)
expect(story.author_credit).to eq("Unlisted Facilitator")
end
end

describe "GET /new" do
it "includes inactive facilitators in the author dropdown" do
inactive_user = create(:user, :with_person, inactive: true)
get new_story_url
expect(response.body).to include(inactive_user.full_name_with_email)
end
end
end

Expand Down
Loading