Skip to content

Commit a7bda7b

Browse files
committed
Add Puzzle validation
1 parent 2101f5a commit a7bda7b

File tree

9 files changed

+143
-22
lines changed

9 files changed

+143
-22
lines changed

app/assets/stylesheets/application.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,17 @@ th {
153153
gap: 0.3em;
154154
}
155155

156+
.field-error {
157+
color: #dc3545;
158+
font-size: 12px;
159+
margin-top: 4px;
160+
font-weight: 500;
161+
}
162+
163+
input.error, textarea.error {
164+
width: 100%;
165+
border-color: #dc3545;
166+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
167+
}
168+
156169

app/controllers/puzzles_controller.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ def edit
1616

1717
def update
1818
@puzzle = Puzzle.find(params[:id])
19-
if @puzzle.update(puzzle_params)
20-
respond_to do |format|
19+
20+
respond_to do |format|
21+
if @puzzle.update(puzzle_params)
2122
format.turbo_stream
2223
format.html { redirect_to puzzles_path, notice: "Puzzle updated." }
2324
format.json { render json: { success: true, puzzle: @puzzle } }
24-
end
25-
else
26-
respond_to do |format|
27-
format.turbo_stream { render turbo_stream: turbo_stream.replace(@puzzle, partial: "puzzles/form", locals: { puzzle: @puzzle }) }
25+
else
26+
format.turbo_stream { render turbo_stream: turbo_stream.replace("modal", partial: "puzzles/edit_modal", locals: { puzzle: @puzzle }), status: :unprocessable_entity }
2827
format.html { render :edit, status: :unprocessable_entity }
2928
format.json { render json: { success: false, errors: @puzzle.errors.full_messages }, status: :unprocessable_entity }
3029
end

app/models/puzzle.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ class Puzzle < ApplicationRecord
33
enum :state, { approved: 0, rejected: 1, pending: 2, archived: 3 }
44
has_many :answers
55

6+
validates :question, presence: true
7+
68
scope :archived, -> { where(state: :archived).order(sent_at: :desc) }
79
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<turbo-frame id="modal">
2+
<div data-controller="modal" data-action="modal#close" class="modal-overlay">
3+
<div data-modal-target="content" class="modal-content" onclick="event.stopPropagation()">
4+
<h2 class="text-xl mb-4">Edit Puzzle</h2>
5+
6+
<%= render "form", puzzle: puzzle %>
7+
8+
<button data-action="modal#close" class="btn close-btn">Close</button>
9+
</div>
10+
</div>
11+
</turbo-frame>

app/views/puzzles/_form.html.erb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
<%=form_with model: @puzzle do |f| %>
1+
<%= form_with model: puzzle do |f| %>
22
<div class="modal-area">
33
<%= f.label :question %>
4-
<%= f.text_area :question %>
4+
<%= f.text_area :question, class: puzzle.errors[:question].any? ? "error" : "" %>
5+
<% if puzzle.errors[:question].any? %>
6+
<div class="field-error"><%= puzzle.errors[:question].first %></div>
7+
<% end %>
58
</div>
69

710
<div class="modal-area">

app/views/puzzles/edit.html.erb

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1 @@
1-
<turbo-frame id="modal">
2-
<div data-controller="modal" data-action="click->modal#close"class="modal-overlay">
3-
<div data-modal-target="content"
4-
class="modal-content"
5-
onclick="event.stopPropagation()">
6-
<h2 class="text-xl mb-4">Edit Puzzle</h2>
7-
8-
<%= render "form", puzzle: @puzzle %>
9-
10-
<button data-action="modal#close"
11-
class="btn close-btn">Close</button>
12-
</div>
13-
</div>
14-
</turbo-frame>
1+
<%= render "edit_modal", puzzle: @puzzle %>

test/controllers/puzzles_controller_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,41 @@ class PuzzlesControllerTest < ActionDispatch::IntegrationTest
55
get puzzles_path
66
assert_response :success
77
end
8+
9+
test "should show error message when editing puzzle with invalid data" do
10+
puzzle = puzzles(:one)
11+
12+
patch puzzle_path(puzzle), params: {
13+
puzzle: {
14+
question: "",
15+
answer: "rails",
16+
explanation: "Updated explanation",
17+
link: "https://example.com"
18+
}
19+
}, as: :turbo_stream
20+
21+
assert_response :unprocessable_entity
22+
23+
assert_select "div.field-error", "can't be blank"
24+
assert_select "textarea.error"
25+
end
26+
27+
test "should successfully update puzzle with valid data" do
28+
puzzle = puzzles(:one)
29+
30+
patch puzzle_path(puzzle), params: {
31+
puzzle: {
32+
question: "Updated question",
33+
answer: "rails",
34+
explanation: "Updated explanation",
35+
link: "https://example.com"
36+
}
37+
}, as: :turbo_stream
38+
39+
assert_response :success
40+
41+
puzzle.reload
42+
assert_equal "Updated question", puzzle.question
43+
assert_equal "Updated explanation", puzzle.explanation
44+
end
845
end

test/fixtures/puzzles.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
one:
2+
question: "Ruby or Rails provided this method? render json: @user"
3+
answer: "rails"
4+
explanation: "This is a test puzzle"
5+
link: "https://example.com"
6+
state: "approved"
7+
sent_at: <%= 1.day.ago %>
8+
suggested_by: "test_user"
9+
10+
two:
11+
question: "Ruby or Rails provided this method? puts 'Hello, World!'"
12+
answer: "ruby"
13+
explanation: "This is a test puzzle 2"
14+
link: ""
15+
state: "pending"
16+
sent_at: <%= 2.days.ago %>
17+
suggested_by: "test_user"

test/models/puzzle_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require "test_helper"
2+
3+
class PuzzleTest < ActiveSupport::TestCase
4+
test "validates presence of question" do
5+
puzzle = Puzzle.new(
6+
question: "What is the capital of France?",
7+
answer: "rails",
8+
explanation: "This is a test puzzle",
9+
link: "https://example.com",
10+
state: "approved",
11+
suggested_by: "test_user"
12+
)
13+
assert puzzle.valid?
14+
end
15+
16+
test "validates question presence - fails without question" do
17+
puzzle = Puzzle.new(
18+
answer: "rails",
19+
explanation: "This is a test puzzle",
20+
link: "https://example.com",
21+
state: "approved",
22+
suggested_by: "test_user"
23+
)
24+
assert_not puzzle.valid?
25+
assert_includes puzzle.errors[:question], "can't be blank"
26+
end
27+
28+
test "defines answer enum with correct values" do
29+
assert_equal 0, Puzzle.answers[:ruby]
30+
assert_equal 1, Puzzle.answers[:rails]
31+
end
32+
33+
test "defines state enum with correct values" do
34+
assert_equal 0, Puzzle.states[:approved]
35+
assert_equal 1, Puzzle.states[:rejected]
36+
assert_equal 2, Puzzle.states[:pending]
37+
assert_equal 3, Puzzle.states[:archived]
38+
end
39+
40+
test "defaults state to pending" do
41+
puzzle = Puzzle.new(
42+
question: "Test question",
43+
answer: "ruby",
44+
explanation: "Test explanation"
45+
)
46+
assert_equal "pending", puzzle.state
47+
end
48+
49+
test "has many answers" do
50+
assert_respond_to Puzzle.new, :answers
51+
end
52+
end

0 commit comments

Comments
 (0)