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.
Todo(name, position, status, parent, children, ...)
SprintTodo(todo_id, sprint_id, position)
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?
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
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
:
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