diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 25a39b1..3913646 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -153,4 +153,17 @@ th {
gap: 0.3em;
}
+.field-error {
+ color: #dc3545;
+ font-size: 12px;
+ margin-top: 4px;
+ font-weight: 500;
+}
+
+input.error, textarea.error {
+ width: 100%;
+ border-color: #dc3545;
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
diff --git a/app/controllers/puzzles_controller.rb b/app/controllers/puzzles_controller.rb
index 72380c5..8771524 100644
--- a/app/controllers/puzzles_controller.rb
+++ b/app/controllers/puzzles_controller.rb
@@ -16,15 +16,14 @@ def edit
def update
@puzzle = Puzzle.find(params[:id])
- if @puzzle.update(puzzle_params)
- respond_to do |format|
+
+ respond_to do |format|
+ if @puzzle.update(puzzle_params)
format.turbo_stream
format.html { redirect_to puzzles_path, notice: "Puzzle updated." }
format.json { render json: { success: true, puzzle: @puzzle } }
- end
- else
- respond_to do |format|
- format.turbo_stream { render turbo_stream: turbo_stream.replace(@puzzle, partial: "puzzles/form", locals: { puzzle: @puzzle }) }
+ else
+ format.turbo_stream { render turbo_stream: turbo_stream.replace("modal", partial: "puzzles/edit_modal", locals: { puzzle: @puzzle }), status: :unprocessable_entity }
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: { success: false, errors: @puzzle.errors.full_messages }, status: :unprocessable_entity }
end
diff --git a/app/models/puzzle.rb b/app/models/puzzle.rb
index c7ee3f5..ebb5c64 100644
--- a/app/models/puzzle.rb
+++ b/app/models/puzzle.rb
@@ -3,5 +3,7 @@ class Puzzle < ApplicationRecord
enum :state, { approved: 0, rejected: 1, pending: 2, archived: 3 }
has_many :answers
+ validates :question, presence: true
+
scope :archived, -> { where(state: :archived).order(sent_at: :desc) }
end
diff --git a/app/views/puzzles/_edit_modal.html.erb b/app/views/puzzles/_edit_modal.html.erb
new file mode 100644
index 0000000..6f33c7a
--- /dev/null
+++ b/app/views/puzzles/_edit_modal.html.erb
@@ -0,0 +1,11 @@
+
+
+
+
Edit Puzzle
+
+ <%= render "form", puzzle: puzzle %>
+
+
+
+
+
diff --git a/app/views/puzzles/_form.html.erb b/app/views/puzzles/_form.html.erb
index a342f8c..8689dba 100644
--- a/app/views/puzzles/_form.html.erb
+++ b/app/views/puzzles/_form.html.erb
@@ -1,7 +1,10 @@
-<%=form_with model: @puzzle do |f| %>
+<%= form_with model: puzzle do |f| %>
<%= f.label :question %>
- <%= f.text_area :question %>
+ <%= f.text_area :question, class: puzzle.errors[:question].any? ? "error" : "" %>
+ <% if puzzle.errors[:question].any? %>
+
<%= puzzle.errors[:question].first %>
+ <% end %>
diff --git a/app/views/puzzles/edit.html.erb b/app/views/puzzles/edit.html.erb
index 1e3fedc..e14a9b0 100644
--- a/app/views/puzzles/edit.html.erb
+++ b/app/views/puzzles/edit.html.erb
@@ -1,14 +1 @@
-
-
-
-
Edit Puzzle
-
- <%= render "form", puzzle: @puzzle %>
-
-
-
-
-
+<%= render "edit_modal", puzzle: @puzzle %>
diff --git a/test/controllers/puzzles_controller_test.rb b/test/controllers/puzzles_controller_test.rb
index bc99568..d7aefe6 100644
--- a/test/controllers/puzzles_controller_test.rb
+++ b/test/controllers/puzzles_controller_test.rb
@@ -5,4 +5,41 @@ class PuzzlesControllerTest < ActionDispatch::IntegrationTest
get puzzles_path
assert_response :success
end
+
+ test "should show error message when editing puzzle with invalid data" do
+ puzzle = puzzles(:one)
+
+ patch puzzle_path(puzzle), params: {
+ puzzle: {
+ question: "",
+ answer: "rails",
+ explanation: "Updated explanation",
+ link: "https://example.com"
+ }
+ }, as: :turbo_stream
+
+ assert_response :unprocessable_entity
+
+ assert_select "div.field-error", "can't be blank"
+ assert_select "textarea.error"
+ end
+
+ test "should successfully update puzzle with valid data" do
+ puzzle = puzzles(:one)
+
+ patch puzzle_path(puzzle), params: {
+ puzzle: {
+ question: "Updated question",
+ answer: "rails",
+ explanation: "Updated explanation",
+ link: "https://example.com"
+ }
+ }, as: :turbo_stream
+
+ assert_response :success
+
+ puzzle.reload
+ assert_equal "Updated question", puzzle.question
+ assert_equal "Updated explanation", puzzle.explanation
+ end
end
diff --git a/test/fixtures/puzzles.yml b/test/fixtures/puzzles.yml
new file mode 100644
index 0000000..2471fec
--- /dev/null
+++ b/test/fixtures/puzzles.yml
@@ -0,0 +1,17 @@
+one:
+ question: "Ruby or Rails provided this method? render json: @user"
+ answer: "rails"
+ explanation: "This is a test puzzle"
+ link: "https://example.com"
+ state: "approved"
+ sent_at: <%= 1.day.ago %>
+ suggested_by: "test_user"
+
+two:
+ question: "Ruby or Rails provided this method? puts 'Hello, World!'"
+ answer: "ruby"
+ explanation: "This is a test puzzle 2"
+ link: ""
+ state: "pending"
+ sent_at: <%= 2.days.ago %>
+ suggested_by: "test_user"
diff --git a/test/models/puzzle_test.rb b/test/models/puzzle_test.rb
new file mode 100644
index 0000000..420e0bb
--- /dev/null
+++ b/test/models/puzzle_test.rb
@@ -0,0 +1,52 @@
+require "test_helper"
+
+class PuzzleTest < ActiveSupport::TestCase
+ test "validates presence of question" do
+ puzzle = Puzzle.new(
+ question: "What is the capital of France?",
+ answer: "rails",
+ explanation: "This is a test puzzle",
+ link: "https://example.com",
+ state: "approved",
+ suggested_by: "test_user"
+ )
+ assert puzzle.valid?
+ end
+
+ test "validates question presence - fails without question" do
+ puzzle = Puzzle.new(
+ answer: "rails",
+ explanation: "This is a test puzzle",
+ link: "https://example.com",
+ state: "approved",
+ suggested_by: "test_user"
+ )
+ assert_not puzzle.valid?
+ assert_includes puzzle.errors[:question], "can't be blank"
+ end
+
+ test "defines answer enum with correct values" do
+ assert_equal 0, Puzzle.answers[:ruby]
+ assert_equal 1, Puzzle.answers[:rails]
+ end
+
+ test "defines state enum with correct values" do
+ assert_equal 0, Puzzle.states[:approved]
+ assert_equal 1, Puzzle.states[:rejected]
+ assert_equal 2, Puzzle.states[:pending]
+ assert_equal 3, Puzzle.states[:archived]
+ end
+
+ test "defaults state to pending" do
+ puzzle = Puzzle.new(
+ question: "Test question",
+ answer: "ruby",
+ explanation: "Test explanation"
+ )
+ assert_equal "pending", puzzle.state
+ end
+
+ test "has many answers" do
+ assert_respond_to Puzzle.new, :answers
+ end
+end