Skip to content

Commit fd60df5

Browse files
committed
feat: Add flash messaging support for checklist item updates in JSON responses
1 parent 0d1cf6e commit fd60df5

File tree

4 files changed

+102
-80
lines changed

4 files changed

+102
-80
lines changed

app/controllers/better_together/checklist_items_controller.rb

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,28 @@ def position # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
9292
respond_to do |format|
9393
format.html { redirect_to request.referer || checklist_path(@checklist), notice: t('flash.generic.updated') }
9494
format.turbo_stream do
95-
# Move the LI node: remove the moved element and insert before/after the sibling
96-
begin
97-
a = @checklist_item
98-
b = sibling
99-
streams = []
100-
streams << turbo_stream.remove(helpers.dom_id(a))
101-
102-
# If direction is up, insert before sibling; if down, insert after sibling
103-
if direction == 'up'
104-
streams << turbo_stream.before(helpers.dom_id(b), partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: a, checklist: @checklist, moved: true })
105-
else
106-
streams << turbo_stream.after(helpers.dom_id(b), partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: a, checklist: @checklist, moved: true })
107-
end
95+
# Move the LI node: remove the moved element and insert before/after the sibling
96+
97+
a = @checklist_item
98+
b = sibling
99+
streams = []
100+
streams << turbo_stream.remove(helpers.dom_id(a))
101+
102+
# If direction is up, insert before sibling; if down, insert after sibling
103+
if direction == 'up'
104+
streams << turbo_stream.before(helpers.dom_id(b),
105+
partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: a, checklist: @checklist, moved: true })
106+
else
107+
streams << turbo_stream.after(helpers.dom_id(b),
108+
partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: a, checklist: @checklist, moved: true })
109+
end
108110

109-
render turbo_stream: streams
110-
rescue StandardError
111-
# Fallback: update only the inner list contents
112-
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
113-
partial: 'better_together/checklist_items/list_contents',
114-
locals: { checklist: @checklist })
115-
end
111+
render turbo_stream: streams
112+
rescue StandardError
113+
# Fallback: update only the inner list contents
114+
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
115+
partial: 'better_together/checklist_items/list_contents',
116+
locals: { checklist: @checklist })
116117
end
117118
end
118119
end
@@ -141,60 +142,57 @@ def reorder # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
141142
respond_to do |format|
142143
format.json { head :no_content }
143144
format.turbo_stream do
144-
# Try a minimal DOM update: if exactly one item moved, remove it and insert before/after the neighbor.
145-
begin
146-
ordered = params[:ordered_ids].map(&:to_i)
147-
# previous_order holds the order before we updated positions
148-
current_before = previous_order
149-
150-
# If nothing changed, no content
151-
if ordered == current_before
152-
head :no_content and return
153-
end
154-
155-
# Detect single moved id (difference between arrays)
156-
moved = (ordered - current_before)
157-
removed = (current_before - ordered)
158-
159-
if moved.size == 1 && removed.size == 1
160-
moved_id = moved.first
161-
moved_item = @checklist.checklist_items.find_by(id: moved_id)
162-
# Safety: if item not found, fallback
163-
unless moved_item
164-
raise 'moved-missing'
165-
end
166-
167-
# Where did it land?
168-
new_index = ordered.index(moved_id)
169-
170-
streams = []
171-
# Remove original node first
172-
streams << turbo_stream.remove(helpers.dom_id(moved_item))
173-
174-
# Append after the next element (neighbor at new_index + 1)
175-
neighbor_id = ordered[new_index + 1] if new_index
176-
if neighbor_id
177-
neighbor = @checklist.checklist_items.find_by(id: neighbor_id)
178-
if neighbor
179-
streams << turbo_stream.after(helpers.dom_id(neighbor), partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: moved_item, checklist: @checklist, moved: true })
180-
render turbo_stream: streams and return
181-
end
182-
end
183-
184-
# If neighbor not found (moved to end), append to the UL
185-
streams << turbo_stream.append("#{helpers.dom_id(@checklist, :checklist_items)} ul", partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: moved_item, checklist: @checklist, moved: true })
145+
# Try a minimal DOM update: if exactly one item moved, remove it and insert before/after the neighbor.
146+
147+
ordered = params[:ordered_ids].map(&:to_i)
148+
# previous_order holds the order before we updated positions
149+
current_before = previous_order
150+
151+
# If nothing changed, no content
152+
head :no_content and return if ordered == current_before
153+
154+
# Detect single moved id (difference between arrays)
155+
moved = (ordered - current_before)
156+
removed = (current_before - ordered)
157+
158+
if moved.size == 1 && removed.size == 1
159+
moved_id = moved.first
160+
moved_item = @checklist.checklist_items.find_by(id: moved_id)
161+
# Safety: if item not found, fallback
162+
raise 'moved-missing' unless moved_item
163+
164+
# Where did it land?
165+
new_index = ordered.index(moved_id)
166+
167+
streams = []
168+
# Remove original node first
169+
streams << turbo_stream.remove(helpers.dom_id(moved_item))
170+
171+
# Append after the next element (neighbor at new_index + 1)
172+
neighbor_id = ordered[new_index + 1] if new_index
173+
if neighbor_id
174+
neighbor = @checklist.checklist_items.find_by(id: neighbor_id)
175+
if neighbor
176+
streams << turbo_stream.after(helpers.dom_id(neighbor),
177+
partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: moved_item, checklist: @checklist, moved: true })
186178
render turbo_stream: streams and return
187179
end
188-
189-
# Fallback: update inner contents for complex reorders
190-
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
191-
partial: 'better_together/checklist_items/list_contents',
192-
locals: { checklist: @checklist })
193-
rescue StandardError
194-
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
195-
partial: 'better_together/checklist_items/list_contents',
196-
locals: { checklist: @checklist })
197180
end
181+
182+
# If neighbor not found (moved to end), append to the UL
183+
streams << turbo_stream.append("#{helpers.dom_id(@checklist, :checklist_items)} ul",
184+
partial: 'better_together/checklist_items/checklist_item', locals: { checklist_item: moved_item, checklist: @checklist, moved: true })
185+
render turbo_stream: streams and return
186+
end
187+
188+
# Fallback: update inner contents for complex reorders
189+
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
190+
partial: 'better_together/checklist_items/list_contents',
191+
locals: { checklist: @checklist })
192+
rescue StandardError
193+
render turbo_stream: turbo_stream.update("#{helpers.dom_id(@checklist, :checklist_items)}",
194+
partial: 'better_together/checklist_items/list_contents',
195+
locals: { checklist: @checklist })
198196
end
199197
end
200198
end

app/controllers/better_together/person_checklist_items_controller.rb

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,32 @@ def create
3737
Rails.logger.info("DBG PersonChecklistItemsController#create: saved pci id=#{pci.id} completed_at=#{pci.completed_at}")
3838
# If checklist completed, trigger a hook (implement as ActiveSupport::Notifications for now)
3939
notify_if_checklist_complete(person)
40-
format.json { render json: { id: pci.id, completed_at: pci.completed_at, flash: { type: 'notice', message: t('flash.checklist_item.updated') } }, status: :ok }
41-
format.html { redirect_back(fallback_location: BetterTogether.base_path_with_locale, notice: t('flash.checklist_item.updated')) }
40+
format.json do
41+
render json: { id: pci.id, completed_at: pci.completed_at, flash: { type: 'notice', message: t('flash.checklist_item.updated') } },
42+
status: :ok
43+
end
44+
format.html do
45+
redirect_back(fallback_location: BetterTogether.base_path_with_locale,
46+
notice: t('flash.checklist_item.updated'))
47+
end
4248
format.turbo_stream do
4349
flash.now[:notice] = t('flash.checklist_item.updated')
44-
render turbo_stream: turbo_stream.replace('flash_messages', partial: 'layouts/better_together/flash_messages', locals: { flash: })
50+
render turbo_stream: turbo_stream.replace('flash_messages',
51+
partial: 'layouts/better_together/flash_messages', locals: { flash: })
4552
end
4653
else
47-
format.json { render json: { errors: pci.errors.full_messages, flash: { type: 'alert', message: t('flash.checklist_item.update_failed') } }, status: :unprocessable_entity }
48-
format.html { redirect_back(fallback_location: BetterTogether.base_path_with_locale, alert: t('flash.checklist_item.update_failed')) }
54+
format.json do
55+
render json: { errors: pci.errors.full_messages, flash: { type: 'alert', message: t('flash.checklist_item.update_failed') } },
56+
status: :unprocessable_entity
57+
end
58+
format.html do
59+
redirect_back(fallback_location: BetterTogether.base_path_with_locale,
60+
alert: t('flash.checklist_item.update_failed'))
61+
end
4962
format.turbo_stream do
5063
flash.now[:alert] = t('flash.checklist_item.update_failed')
51-
render turbo_stream: turbo_stream.replace('flash_messages', partial: 'layouts/better_together/flash_messages', locals: { flash: })
64+
render turbo_stream: turbo_stream.replace('flash_messages',
65+
partial: 'layouts/better_together/flash_messages', locals: { flash: })
5266
end
5367
end
5468
end

spec/requests/better_together/person_checklist_items_json_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,9 @@
1818
expect(response).to have_http_status(:ok)
1919
body = JSON.parse(response.body)
2020
expect(body['completed_at']).not_to be_nil
21+
# New: server includes a flash payload in JSON for client-side display
22+
expect(body['flash']).to be_present
23+
expect(body['flash']['type']).to eq('notice')
24+
expect(body['flash']['message']).to eq(I18n.t('flash.checklist_item.updated'))
2125
end
2226
end

spec/requests/better_together/person_checklist_items_spec.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require 'rails_helper'
22

3-
RSpec.describe BetterTogether::PersonChecklistItemsController, type: :request do
3+
RSpec.describe BetterTogether::PersonChecklistItemsController, type: :request, as_user: true do
44
include Devise::Test::IntegrationHelpers
55

66
let(:user) { create(:user) }
@@ -9,6 +9,7 @@
99
let(:items) { create_list(:better_together_checklist_item, 3, checklist: checklist) }
1010

1111
before do
12+
configure_host_platform
1213
# Use project's HTTP login helper to satisfy route constraints
1314
test_user = find_or_create_test_user(user.email, 'password12345', :user)
1415
login(test_user.email, 'password12345')
@@ -19,10 +20,15 @@
1920
expect(response).to have_http_status(:ok)
2021
expect(JSON.parse(response.body)['completed_at']).to be_nil
2122

22-
patch "/#{I18n.default_locale}/#{BetterTogether.route_scope_path}/checklists/#{checklist.id}/checklist_items/#{items.first.id}/person_checklist_item",
23-
params: { completed: true }
23+
post "/#{I18n.default_locale}/#{BetterTogether.route_scope_path}/checklists/#{checklist.id}/checklist_items/#{items.first.id}/person_checklist_item",
24+
params: { completed: true }, as: :json
2425
expect(response).to have_http_status(:ok)
2526
data = JSON.parse(response.body)
2627
expect(data['completed_at']).not_to be_nil
28+
29+
# Expect flash payload for client-side display
30+
expect(data['flash']).to be_present
31+
expect(data['flash']['type']).to eq('notice')
32+
expect(data['flash']['message']).to eq(I18n.t('flash.checklist_item.updated'))
2733
end
2834
end

0 commit comments

Comments
 (0)