summaryrefslogtreecommitdiff
path: root/app/models/concerns/relative_positioning.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns/relative_positioning.rb')
-rw-r--r--app/models/concerns/relative_positioning.rb52
1 files changed, 36 insertions, 16 deletions
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index e4fe46d722a..9cd7b8d6258 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -1,5 +1,26 @@
# frozen_string_literal: true
+# This module makes it possible to handle items as a list, where the order of items can be easily altered
+# Requirements:
+#
+# - Only works for ActiveRecord models
+# - relative_position integer field must present on the model
+# - This module uses GROUP BY: the model should have a parent relation, example: project -> issues, project is the parent relation (issues table has a parent_id column)
+#
+# Setup like this in the body of your class:
+#
+# include RelativePositioning
+#
+# # base query used for the position calculation
+# def self.relative_positioning_query_base(issue)
+# where(deleted: false)
+# end
+#
+# # column that should be used in GROUP BY
+# def self.relative_positioning_parent_column
+# :project_id
+# end
+#
module RelativePositioning
extend ActiveSupport::Concern
@@ -93,7 +114,7 @@ module RelativePositioning
return move_after(before) unless after
return move_before(after) unless before
- # If there is no place to insert an issue we need to create one by moving the before issue closer
+ # If there is no place to insert an item we need to create one by moving the before item closer
# to its predecessor. This process will recursively move all the predecessors until we have a place
if (after.relative_position - before.relative_position) < 2
before.move_before
@@ -108,11 +129,11 @@ module RelativePositioning
pos_after = before.next_relative_position
if before.shift_after?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after)
- issue_to_move.move_after
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_after)
+ item_to_move.move_after
+ @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
- pos_after = issue_to_move.relative_position
+ pos_after = item_to_move.relative_position
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -123,11 +144,11 @@ module RelativePositioning
pos_before = after.prev_relative_position
if after.shift_before?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before)
- issue_to_move.move_before
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_before)
+ item_to_move.move_before
+ @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
- pos_before = issue_to_move.relative_position
+ pos_before = item_to_move.relative_position
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -141,13 +162,13 @@ module RelativePositioning
self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
- # Indicates if there is an issue that should be shifted to free the place
+ # Indicates if there is an item that should be shifted to free the place
def shift_after?
next_pos = next_relative_position
next_pos && (next_pos - relative_position) == 1
end
- # Indicates if there is an issue that should be shifted to free the place
+ # Indicates if there is an item that should be shifted to free the place
def shift_before?
prev_pos = prev_relative_position
prev_pos && (relative_position - prev_pos) == 1
@@ -159,7 +180,7 @@ module RelativePositioning
def save_positionable_neighbours
return unless @positionable_neighbours
- status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
+ status = @positionable_neighbours.all? { |item| item.save(touch: false) }
@positionable_neighbours = nil
status
@@ -170,16 +191,15 @@ module RelativePositioning
# When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977
- relation = self.class
- .in_parents(parent_ids)
+ relation = self.class.relative_positioning_query_base(self)
.order(Gitlab::Database.nulls_last_order('position', 'DESC'))
+ .group(self.class.relative_positioning_parent_column)
.limit(1)
- .group(self.class.parent_column)
relation = yield relation if block_given?
relation
- .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
+ .pluck(self.class.relative_positioning_parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&.
last
end