Skip to content

Commit 9cc5272

Browse files
committed
feat: Refactor checklist item rendering and caching; improve helper methods for better performance
1 parent 0977a48 commit 9cc5272

File tree

6 files changed

+58
-21
lines changed

6 files changed

+58
-21
lines changed

app/controllers/better_together/checklist_items_controller.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class ChecklistItemsController < FriendlyResourceController # rubocop:todo Style
66
before_action :checklist_item, only: %i[show edit update destroy position]
77

88
helper_method :new_checklist_item
9+
helper_method :checklist_items_for
910

1011
def create # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
1112
@checklist_item = new_checklist_item
@@ -94,9 +95,8 @@ def position # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
9495
format.turbo_stream do
9596
render turbo_stream: turbo_stream.replace(
9697
helpers.dom_id(@checklist, :checklist_items),
97-
partial: 'better_together/checklist_items/checklist_item',
98-
collection: @checklist.checklist_items.with_translations,
99-
as: :checklist_item
98+
partial: 'better_together/checklist_items/list',
99+
locals: { checklist: @checklist }
100100
)
101101
end
102102
end
@@ -125,9 +125,8 @@ def reorder # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
125125
format.turbo_stream do
126126
render turbo_stream: turbo_stream.replace(
127127
helpers.dom_id(@checklist, :checklist_items),
128-
partial: 'better_together/checklist_items/checklist_item',
129-
collection: @checklist.checklist_items.with_translations,
130-
as: :checklist_item
128+
partial: 'better_together/checklist_items/list',
129+
locals: { checklist: @checklist }
131130
)
132131
end
133132
end
@@ -182,5 +181,27 @@ def resource_class
182181
def resource_collection
183182
resource_class.where(checklist: @checklist)
184183
end
184+
185+
# Returns a memoized relation (or array) of checklist items for a checklist and optional parent_id.
186+
# Views should call this helper instead of building policy_scope queries inline so ordering and
187+
# policy scoping remain consistent and memoized for a single request.
188+
def checklist_items_for(checklist, parent_id: nil) # rubocop:disable Metrics/MethodLength
189+
@__checklist_items_cache ||= {}
190+
key = [checklist.id, parent_id]
191+
return @__checklist_items_cache[key] if @__checklist_items_cache.key?(key)
192+
193+
scope = policy_scope(::BetterTogether::ChecklistItem)
194+
scope = scope.where(checklist: checklist)
195+
scope = if parent_id.nil?
196+
scope.where(parent_id: nil)
197+
else
198+
scope.where(parent_id: parent_id)
199+
end
200+
201+
# Ensure we enforce ordering by position regardless of any order applied by policy_scope
202+
scope = scope.with_translations.reorder(:position)
203+
204+
@__checklist_items_cache[key] = scope
205+
end
185206
end
186207
end

app/helpers/better_together/checklist_items_helper.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,25 @@
33
module BetterTogether
44
# Helper methods for rendering and formatting checklist items in views.
55
#
6-
# Provides small view helper utilities used by checklist-related templates.
6+
# Provides view helper utilities used by checklist-related templates.
77
module ChecklistItemsHelper
8+
# Return a relation of checklist items scoped for the provided checklist and optional parent_id.
9+
# This helper is available in views and mirrors the controller helper implementation; it
10+
# ensures ordering by position and memoizes the relation per-request.
11+
def checklist_items_for(checklist, parent_id: nil)
12+
@__checklist_items_cache ||= {}
13+
key = [checklist.id, parent_id]
14+
return @__checklist_items_cache[key] if @__checklist_items_cache.key?(key)
15+
16+
scope = policy_scope(::BetterTogether::ChecklistItem)
17+
scope = scope.where(checklist: checklist)
18+
scope = parent_id.nil? ? scope.where(parent_id: nil) : scope.where(parent_id: parent_id)
19+
20+
scope = scope.with_translations.reorder(:position)
21+
22+
@__checklist_items_cache[key] = scope
23+
end
24+
825
# Build an option title for a checklist item including depth-based prefix and slug.
926
# Example: "— — Subitem label (subitem-slug)"
1027
def checklist_item_option_title(item)

app/views/better_together/checklist_items/_checklist_item.html.erb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@
8686
<%# Render children container (always present so Turbo can target it); CSS hides it when empty %>
8787
<% has_children = checklist_item.children.exists? %>
8888
<ul id="<%= dom_id(checklist_item, :children) %>" class="list-group ms-4 children_checklist_item <%= dom_class(checklist_item, :children) %>" data-has-children="<%= has_children ? 'true' : 'false' %>">
89-
<% child_items = policy_scope(::BetterTogether::ChecklistItem).where(checklist: checklist_item.checklist, parent_id: checklist_item.id).with_translations.order(:position) %>
90-
<%= render partial: 'better_together/checklist_items/checklist_item', collection: child_items, as: :checklist_item %>
89+
<%# Prefer a provided `items` local (passed down by parent), otherwise use controller helper which memoizes policy_scope + reorder. Fallback to explicit policy_scope with reorder if helper is unavailable in this context. %>
90+
<% children_items ||= checklist_items_for(checklist_item.checklist, parent_id: checklist_item.id) %>
91+
<%= render partial: 'better_together/checklist_items/checklist_item', collection: children_items, as: :checklist_item %>
9192
</ul>
9293
</li>
9394

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<%# app/views/better_together/checklist_items/_list.html.erb %>
22
<div id="<%= dom_id(checklist, :checklist_items) %>" class="bt-list-fade" data-better_together--checklist-items-target="list" data-bt-debug="true" data-can-update="<%= policy(checklist).update? ? 'true' : 'false' %>">
33
<ul class="list-group bt-checklist" role="list" aria-labelledby="checklist-items-list">
4-
<% items = policy_scope(::BetterTogether::ChecklistItem).where(checklist: checklist, parent_id: nil).with_translations.order(:position) %>
4+
<%# Use provided items local when available (e.g., from a parent partial), otherwise fallback to controller helper %>
5+
<% items ||= checklist_items_for(checklist, parent_id: nil) %>
56
<%= render partial: 'better_together/checklist_items/checklist_item', collection: items, as: :checklist_item %>
67
</ul>
78
</div>

app/views/better_together/checklist_items/_list_contents.html.erb

Lines changed: 0 additions & 7 deletions
This file was deleted.

spec/models/better_together/checklist_item_position_spec.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
end
1616

1717
# create a new item without position - Positioned#set_position should set it to 5
18-
new_item = create(:better_together_checklist_item, checklist: checklist, privacy: 'public',
19-
label: 'Appended Model Item')
18+
# create a new item without a preset position so Positioned#set_position runs
19+
new_item = build(:better_together_checklist_item, checklist: checklist, privacy: 'public',
20+
label: 'Appended Model Item')
21+
# ensure factory default position (0) is not applied by setting to nil before save
22+
new_item.position = nil
23+
new_item.save!
2024

2125
expect(new_item.position).to eq(5)
2226

23-
# ordering check
24-
ordered = checklist.checklist_items.order(:position).pluck(:label, :position)
27+
# ordering check (use Ruby accessors because label is translated and not a DB column)
28+
ordered = checklist.checklist_items.order(:position).to_a.map { |ci| [ci.label, ci.position] }
2529
expect(ordered.last.first).to eq('Appended Model Item')
2630
end
2731
end

0 commit comments

Comments
 (0)