relative_positioning.rb 3.0 KB
Newer Older
1 2 3
module RelativePositioning
  extend ActiveSupport::Concern

4 5
  MIN_POSITION = 0
  MAX_POSITION = Gitlab::Database::MAX_INT_VALUE
6 7 8 9 10 11

  included do
    after_save :save_positionable_neighbours
  end

  def min_relative_position
12
    self.class.in_projects(project.id).minimum(:relative_position)
13 14 15
  end

  def max_relative_position
16
    self.class.in_projects(project.id).maximum(:relative_position)
17 18 19 20 21 22 23
  end

  def prev_relative_position
    prev_pos = nil

    if self.relative_position
      prev_pos = self.class.
24
        in_projects(project.id).
25 26 27 28 29 30 31 32 33 34 35 36
        where('relative_position < ?', self.relative_position).
        maximum(:relative_position)
    end

    prev_pos || MIN_POSITION
  end

  def next_relative_position
    next_pos = nil

    if self.relative_position
      next_pos = self.class.
37
        in_projects(project.id).
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
        where('relative_position > ?', self.relative_position).
        minimum(:relative_position)
    end

    next_pos || MAX_POSITION
  end

  def move_between(before, after)
    return move_after(before) if before && !after
    return move_before(after) if after && !before

    pos_before = before.relative_position
    pos_after = after.relative_position

    if pos_before && pos_after
      if pos_before == pos_after
V
Valery Sizov 已提交
54
        self.relative_position = pos_before
55 56
        before.move_before(self)
        after.move_after(self)
V
Valery Sizov 已提交
57

58 59 60 61 62 63 64
        @positionable_neighbours = [before, after]
      else
        self.relative_position = position_between(pos_before, pos_after)
      end
    elsif pos_before
      self.move_after(before)
      after.move_after(self)
V
Valery Sizov 已提交
65

66 67 68 69
      @positionable_neighbours = [after]
    elsif pos_after
      self.move_before(after)
      before.move_before(self)
V
Valery Sizov 已提交
70

71 72 73 74 75
      @positionable_neighbours = [before]
    else
      move_to_end
      before.move_before(self)
      after.move_after(self)
V
Valery Sizov 已提交
76

77 78 79 80 81 82
      @positionable_neighbours = [before, after]
    end
  end

  def move_before(after)
    pos_after = after.relative_position
V
Valery Sizov 已提交
83

84
    if pos_after
85
      self.relative_position = position_between(MIN_POSITION, pos_after)
86 87 88
    else
      move_to_end
      after.move_after(self)
V
Valery Sizov 已提交
89

90 91 92 93 94 95
      @positionable_neighbours = [after]
    end
  end

  def move_after(before)
    pos_before = before.relative_position
V
Valery Sizov 已提交
96

97
    if pos_before
98
      self.relative_position = position_between(pos_before, MAX_POSITION)
99 100 101
    else
      move_to_end
      before.move_before(self)
V
Valery Sizov 已提交
102

103 104 105 106 107
      @positionable_neighbours = [before]
    end
  end

  def move_to_end
V
Valery Sizov 已提交
108
    self.relative_position = position_between(max_relative_position, MAX_POSITION)
109 110 111 112 113 114 115 116 117
  end

  def move_between!(*args)
    move_between(*args) && save!
  end

  private

  def position_between(pos_before, pos_after)
V
Valery Sizov 已提交
118 119 120
    pos_before ||= MIN_POSITION
    pos_after ||= MAX_POSITION

121 122
    pos_before, pos_after = [pos_before, pos_after].sort

123
    rand(pos_before.next..pos_after.pred)
124 125 126 127 128
  end

  def save_positionable_neighbours
    return unless @positionable_neighbours

V
Valery Sizov 已提交
129
    status = @positionable_neighbours.all?(&:save)
130 131
    @positionable_neighbours = nil

V
Valery Sizov 已提交
132
    status
133 134
  end
end