Use associations for task and series ownership scoping
many_through_many requires right_primary_key: :series_id
because Sequel defaults to the associated model's primary key.

Assisted-by: Claude Opus 4.6 via Claude Code
change ukvmytnruomkptwqxvvroszknzntsyuv
commit 2f1acb125ef133a7e289ccf965e99cb444f88cb6
author Alpha Chen <alpha@kejadlen.dev>
date
parent pwvqvlpr
diff --git a/lib/ketchup/models.rb b/lib/ketchup/models.rb
index 399ec55..f5837d5 100644
--- a/lib/ketchup/models.rb
+++ b/lib/ketchup/models.rb
@@ -4,9 +4,11 @@ require_relative "db"
 
 Sequel::Model.plugin :timestamps, update_on_create: true
 Sequel::Model.plugin :sole
+Sequel::Model.plugin :many_through_many
 
 class User < Sequel::Model
   one_to_many :series
+  many_through_many :tasks, [[:series, :user_id, :id]], right_primary_key: :series_id
 
   def active_tasks
     Task.active.for_user(self)
diff --git a/lib/ketchup/web.rb b/lib/ketchup/web.rb
index 3ebe052..f1acd10 100644
--- a/lib/ketchup/web.rb
+++ b/lib/ketchup/web.rb
@@ -20,29 +20,26 @@ class Web < Roda
   end
 
   route do |r|
-    r.halt 403 unless current_user
+    @user = current_user
+    r.halt 403 unless @user
 
     r.root do
-      Views::Dashboard.new(current_user:).call
+      Views::Dashboard.new(current_user: @user).call
     end
 
     r.on "tasks", Integer do |task_id|
+      @task = @user.tasks_dataset.first(Sequel[:tasks][:id] => task_id)
+      r.halt 404 unless @task
+
       r.post "complete" do
-        task = Task.active.for_user(current_user).where(Sequel[:tasks][:id] => task_id).first
-        r.halt 404 unless task
-        task.complete!
+        r.halt 422 unless @task[:completed_at].nil?
+        @task.complete!
 
-        r.redirect "/series/#{task[:series_id]}"
+        r.redirect "/series/#{@task[:series_id]}"
       end
 
       r.patch "note" do
-        task = Task.join(:series, id: :series_id)
-          .where(Sequel[:series][:user_id] => current_user.id)
-          .where(Sequel[:tasks][:id] => task_id)
-          .select_all(:tasks)
-          .first
-        r.halt 404 unless task
-        r.halt 422 if task[:completed_at].nil?
+        r.halt 422 if @task[:completed_at].nil?
 
         note = r.params["note"].to_s.strip
         Task.where(id: task_id).update(note: note.empty? ? nil : note)
@@ -53,11 +50,11 @@ class Web < Roda
     end
 
     r.on "series", Integer do |series_id|
-      series = Series.where(id: series_id, user_id: current_user.id).first
+      series = @user.series_dataset[series_id]
       r.halt 404 unless series
 
       r.get do
-        Views::Dashboard.new(current_user:, series:).call
+        Views::Dashboard.new(current_user: @user, series:).call
       end
 
       r.patch do
@@ -120,7 +117,7 @@ class Web < Roda
         end
 
         series = Series.create_with_first_task(
-          user: current_user,
+          user: @user,
           note: note,
           interval_unit: interval_unit,
           interval_count: interval_count,