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
9 changes: 8 additions & 1 deletion app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ def index
.published
.includes(:user)
.order(created_at: :desc)
@testimonials = Testimonial.published.includes(:user).order(Arel.sql("RANDOM()")).limit(20)
subquery = Testimonial.published
.select("testimonials.*", "ROW_NUMBER() OVER (PARTITION BY LOWER(heading) ORDER BY RANDOM()) AS rn")
@testimonials = Testimonial
.from(subquery, :testimonials)
.where("rn = 1")
.order(Arel.sql("RANDOM()"))
.limit(10)
.includes(:user)
end
end
17 changes: 12 additions & 5 deletions app/jobs/generate_testimonial_fields_job.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class GenerateTestimonialFieldsJob < ApplicationJob
queue_as :default

MAX_HEADING_RETRIES = 3
MAX_HEADING_RETRIES = 5

def perform(testimonial)
existing_headings = Testimonial.where.not(id: testimonial.id).where.not(heading: nil).pluck(:heading)
Expand Down Expand Up @@ -41,6 +41,10 @@ def perform(testimonial)
return
end

if heading_taken?(parsed["heading"], testimonial.id)
Rails.logger.warn "Testimonial #{testimonial.id}: heading '#{parsed["heading"]}' still collides after #{MAX_HEADING_RETRIES} retries, saving anyway"
end

testimonial.update!(
heading: parsed["heading"],
subheading: parsed["subheading"],
Expand All @@ -66,10 +70,13 @@ def build_system_prompt(existing_headings)
You generate structured testimonial content for a Ruby programming language advocacy site.
Given a user's quote about why they love Ruby, generate:

1. heading: A single unique 1-2 word heading that captures the THEME of the quote (e.g., "Elegance", "Joy", "Craft").
1. heading: A unique 1-3 word heading that captures the THEME or FEELING of the quote.
Be creative and specific. Go beyond generic words. Think of evocative nouns, metaphors, compound phrases, or poetic concepts.
The heading must make sense as an answer to "Why Ruby?" — e.g. "Why Ruby?" → "Flow State", "Clarity", "Pure Joy".
Good examples: "Spark", "Flow State", "Quiet Power", "Warm Glow", "First Love", "Playground", "Second Nature", "Deep Roots", "Readable Code", "Clean Slate", "Smooth Sailing", "Expressiveness", "Old Friend", "Sharp Tools", "Creative Freedom", "Solid Ground", "Calm Waters", "Poetic Logic", "Builder's Joy", "Sweet Spot", "Hidden Gem", "Fresh Start", "True North", "Clarity", "Belonging", "Empowerment", "Momentum", "Simplicity", "Trust", "Confidence"
#{taken}
2. subheading: A short tagline under 10 words.
3. body_text: 2-3 sentences that EXTEND and DEEPEN the user's idea — add new angles, examples, or implications.
3. body_text: 2-3 sentences that EXTEND and DEEPEN the user's idea. Add new angles, examples, or implications.
Do NOT repeat or paraphrase what the user already said. Build on top of it.

WRITING STYLE — sound like a real person, not an AI:
Expand Down Expand Up @@ -136,7 +143,7 @@ def generate_with_anthropic(system_prompt, user_prompt)
parameters: {
model: "claude-3-haiku-20240307",
max_tokens: 300,
temperature: 0.7,
temperature: 0.8,
system: system_prompt,
messages: [ { role: "user", content: user_prompt } ]
}
Expand All @@ -161,7 +168,7 @@ def generate_with_openai(system_prompt, user_prompt)
{ role: "system", content: system_prompt },
{ role: "user", content: user_prompt }
],
temperature: 0.7,
temperature: 0.8,
max_tokens: 300
}
)
Expand Down
5 changes: 2 additions & 3 deletions app/jobs/validate_testimonial_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ def perform(testimonial)
VALIDATION RULES:
1. First check the user's QUOTE against the content policy. If it violates (including being negative about Ruby), reject immediately with reject_reason "quote".
2. If the quote is fine, check the AI-generated fields (heading/subheading/body). ONLY reject generation if there is a CLEAR problem:
- The heading duplicates an existing one listed below
- The body contradicts or misrepresents the quote
- The subheading is nonsensical or unrelated
- The content is factually wrong about Ruby
Do NOT reject just because the fields could be "better" or "more creative". Good enough is good enough — publish it.
Do NOT reject for duplicate headings (handled elsewhere). Do NOT reject just because the fields could be "better" or "more creative". Good enough is good enough — publish it.
3. If everything looks acceptable, publish it.

AI-SOUNDING LANGUAGE CHECK:
Expand All @@ -37,7 +36,7 @@ def perform(testimonial)
- Superficial -ing tack-ons ("ensuring...", "highlighting...", "fostering...")
If the quote itself is fine but the generated text sounds like AI wrote it, set reject_reason to "generation" and explain which phrases sound artificial.

Existing published testimonials (avoid duplicate headings/themes):
Existing published testimonials (for context):
#{existing.presence || "None yet."}

Respond with valid JSON only: {"publish": true/false, "reject_reason": "quote" or "generation" or null, "feedback": "..."}
Expand Down
1 change: 0 additions & 1 deletion app/models/testimonial.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ class Testimonial < ApplicationRecord

validates :quote, length: { minimum: 140, maximum: 320 }, allow_blank: true
validates :user_id, uniqueness: true
validates :heading, uniqueness: true, allow_nil: true

scope :published, -> { where(published: true) }
scope :ordered, -> { order(Arel.sql("position ASC NULLS LAST, created_at DESC")) }
Expand Down
8 changes: 4 additions & 4 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@
<% else %>
<% Category.with_posts.ordered.each do |category| %>
<% if category.is_success_story? && has_success_stories? %>
<%= link_to category.name, category_path(category),
<%= link_to category.name, main_site_url(category_path(category)),
class: "#{category_menu_active?(category) ? 'bg-red-100 text-red-700' : 'text-gray-600 hover:bg-gray-100'} px-3 py-2 rounded-md text-sm font-medium whitespace-nowrap" %>
<% elsif !category.is_success_story? %>
<%= link_to category.name, category_path(category),
<%= link_to category.name, main_site_url(category_path(category)),
class: "#{category_menu_active?(category) ? 'bg-red-100 text-red-700' : 'text-gray-600 hover:bg-gray-100'} px-3 py-2 rounded-md text-sm font-medium whitespace-nowrap" %>
<% end %>
<% end %>
Expand Down Expand Up @@ -202,10 +202,10 @@
<% else %>
<% Category.with_posts.ordered.limit(7).each do |category| %>
<% if category.is_success_story? && has_success_stories? %>
<%= link_to category.name, category_path(category),
<%= link_to category.name, main_site_url(category_path(category)),
class: "#{category_menu_active?(category) ? 'bg-red-100 text-red-700' : 'text-gray-600 hover:bg-gray-100'} block px-3 py-2 rounded-md text-base font-medium" %>
<% elsif !category.is_success_story? %>
<%= link_to category.name, category_path(category),
<%= link_to category.name, main_site_url(category_path(category)),
class: "#{category_menu_active?(category) ? 'bg-red-100 text-red-700' : 'text-gray-600 hover:bg-gray-100'} block px-3 py-2 rounded-md text-base font-medium" %>
<% end %>
<% end %>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class RemoveUniqueIndexFromTestimonialsHeading < ActiveRecord::Migration[8.2]
def change
remove_index :testimonials, :heading, unique: true
add_index :testimonials, :heading
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RemoveEmailNotNullConstraintFromUsers < ActiveRecord::Migration[8.2]
def change
change_column_null :users, :email, true
end
end
6 changes: 3 additions & 3 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions test/models/testimonial_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ class TestimonialTest < ActiveSupport::TestCase
assert_includes duplicate.errors[:user_id], "has already been taken"
end

test "validates uniqueness of heading allowing nil" do
test "allows duplicate headings" do
existing = testimonials(:published)
other_user = users(:user_no_testimonial)
new_testimonial = Testimonial.new(
user: other_user,
quote: "My quote",
quote: "I love Ruby because it makes programming feel like poetry. The syntax reads so naturally that you can focus on solving problems instead of fighting the language. It truly is a joy.",
heading: existing.heading
)
assert_not new_testimonial.valid?
assert_includes new_testimonial.errors[:heading], "has already been taken"
assert new_testimonial.valid?
end

test "allows nil heading" do
Expand Down