1

TLDR: Is there a way to scope acts_as_list into another table as such

class SprintTodo < ApplicationRecord
  belongs_to :sprint
  belongs_to :todo
  acts_as_list scope: [:sprint, :todo.status]
end

I have two tables with one joining table.

  1. Todo(name, position, status, parent, children, ...)
  2. SprintTodo(todo_id, sprint_id, position)
  3. Sprint(name, start_date, end_date, ...)

Todo has its own position based on its parents (tree) while SprintTodo holds the position as in Kanban Board based on its status.

The problem I am facing right now is that I cannot reach into Todo table to scope it that way. One solution (although a bad one) is to replicate Todo status in SprintTodo as well but that would be bad design.

Is there any other way I can scope it on status?

1 Answer 1

1

It'll probably be simpler to add a status column to SprintTodo instead. But there is a way:

class SprintTodo < ApplicationRecord
  belongs_to :todo
  belongs_to :sprint

  acts_as_list scope: "sprint_todos.id IN (\#{todo_status_sql}) AND sprint_todos.sprint_id = \#{sprint_id}"

  def todo_status_sql
    SprintTodo.select(:id).joins(:todo).where(todo: { status: todo.status }).to_sql
  end
end
Sprint.create!
Todo.create!([{ status: :one }, { status: :one }, { status: :two }, { status: :two }])
Sprint.first.todos << Todo.all
Sprint.create!
Sprint.second.todos << Todo.create(status: :one)


>> SprintTodo.all.as_json(only: [:position, :sprint_id], include: {todo: {only: [:status, :id]}})
=> 
[{"position"=>1, "sprint_id"=>1, "todo"=>{"id"=>1, "status"=>"one"}},
 {"position"=>2, "sprint_id"=>1, "todo"=>{"id"=>2, "status"=>"one"}},

 {"position"=>1, "sprint_id"=>1, "todo"=>{"id"=>3, "status"=>"two"}},
 {"position"=>2, "sprint_id"=>1, "todo"=>{"id"=>4, "status"=>"two"}},

 {"position"=>1, "sprint_id"=>2, "todo"=>{"id"=>5, "status"=>"one"}}]
#             ^               ^                               ^
#     positioned        by sprint                 and todo.status  

https://www.rubydoc.info/gems/acts_as_list/0.8.2/ActiveRecord/Acts/List/ClassMethods#acts_as_list-instance_method


Update

I didn't see anything in acts_as_list code to support reordering on status change. The change happens in Todo, but all the callbacks to update position are in SprintTodo:

https://github.com/brendon/acts_as_list/blob/v1.1.0/lib/acts_as_list/active_record/acts/callback_definer.rb#L6-L16

First approach is just create a new SprintTodo:

class Todo < ApplicationRecord
  has_many :sprint_todos

  after_update do
    new_sprint_todo = sprint_todos.first.dup
    sprint_todos.first.destroy
    new_sprint_todo.position = nil
    new_sprint_todo.save!
  end
end

The other way is to trigger some of those callbacks manually:

class SprintTodo < ApplicationRecord
  attr_accessor :scope_changed
  #...
end
class Todo < ApplicationRecord
  has_many :sprint_todos

  before_update do
    sprint_todo = sprint_todos.first
    sprint_todo.scope_changed = true
    sprint_todo.send :check_scope
    sprint_todo.save!
  end
end

Have to set @scope_changed, otherwise check_scope won't do anything:
https://github.com/brendon/acts_as_list/blob/v1.1.0/lib/acts_as_list/active_record/acts/list.rb#L430-L441

3
  • I agree. The reason I am not adding status to SprintTodos is status is part of a Todo, not SprintTodo. It would create a weird situation for a Todo that has not been assigned a Sprint.
    – Sawaid
    Commented Jan 29, 2023 at 15:46
  • One more thing, if I change the status of Todo, it doesn't automatically update the position of it in SprintTodo. Is there any way to force reset its position?
    – Sawaid
    Commented Feb 5, 2023 at 10:50
  • @Sawaid see update. i'm not sure how well this will work, you probably have to tweak it a bit to work with your code.
    – Alex
    Commented Feb 10, 2023 at 2:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.