Class: Gitlab::RelativePositioning::ItemContext

Inherits:
Object
  • Object
show all
Includes:
Utils::StrongMemoize
Defined in:
lib/gitlab/relative_positioning/item_context.rb

Overview

This class is API private - it should not be explicitly instantiated outside of tests rubocop: disable CodeReuse/ActiveRecord

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, range, ignoring: nil) ⇒ ItemContext

Returns a new instance of ItemContext.



14
15
16
17
18
19
# File 'lib/gitlab/relative_positioning/item_context.rb', line 14

def initialize(object, range, ignoring: nil)
  @object = object
  @range = range
  @model_class = object.class
  @ignoring = ignoring
end

Instance Attribute Details

#ignoringObject

Returns the value of attribute ignoring.



12
13
14
# File 'lib/gitlab/relative_positioning/item_context.rb', line 12

def ignoring
  @ignoring
end

#model_classObject (readonly)

Returns the value of attribute model_class.



11
12
13
# File 'lib/gitlab/relative_positioning/item_context.rb', line 11

def model_class
  @model_class
end

#objectObject (readonly)

Returns the value of attribute object.



11
12
13
# File 'lib/gitlab/relative_positioning/item_context.rb', line 11

def object
  @object
end

#rangeObject (readonly)

Returns the value of attribute range.



11
12
13
# File 'lib/gitlab/relative_positioning/item_context.rb', line 11

def range
  @range
end

Instance Method Details

#==(other) ⇒ Object



21
22
23
# File 'lib/gitlab/relative_positioning/item_context.rb', line 21

def ==(other)
  other.is_a?(self.class) && other.object == object && other.range == range && other.ignoring == ignoring
end

#at_position(position) ⇒ Object

Raises:



117
118
119
120
121
122
123
# File 'lib/gitlab/relative_positioning/item_context.rb', line 117

def at_position(position)
  item = scoped_items.find_by(relative_position: position)

  raise InvalidPosition, 'No item found at the specified position' if item.nil?

  neighbour(item)
end

#calculate_relative_position(calculation) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/gitlab/relative_positioning/item_context.rb', line 82

def calculate_relative_position(calculation)
  # 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-foss/issues/54276#note_119340977
  relation = scoped_items
               .order(Arel.sql('position').desc.nulls_last)
               .group(grouping_column)
               .limit(1)

  relation = yield relation if block_given?

  relation
    .pick(grouping_column, Arel.sql("#{calculation}(relative_position) AS position"))&.last
end

#create_space_leftObject



135
136
137
# File 'lib/gitlab/relative_positioning/item_context.rb', line 135

def create_space_left
  find_next_gap_before.tap { |gap| move_sequence_before(false, next_gap: gap) }
end

#create_space_rightObject



139
140
141
# File 'lib/gitlab/relative_positioning/item_context.rb', line 139

def create_space_right
  find_next_gap_after.tap { |gap| move_sequence_after(false, next_gap: gap) }
end

#find_next_gap(items_with_next_pos, default_end) ⇒ Object



161
162
163
164
165
166
167
168
169
170
# File 'lib/gitlab/relative_positioning/item_context.rb', line 161

def find_next_gap(items_with_next_pos, default_end)
  gap = model_class
    .from(items_with_next_pos, :items)
    .where('next_pos IS NULL OR ABS(pos::bigint - next_pos::bigint) >= ?', MIN_GAP)
    .pick(:pos, :next_pos)

  return if gap.nil? || gap.first == default_end

  Gap.new(gap.first, gap.second || default_end)
end

#find_next_gap_afterObject



152
153
154
155
156
157
158
159
# File 'lib/gitlab/relative_positioning/item_context.rb', line 152

def find_next_gap_after
  items_with_next_pos = scoped_items
                          .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position ASC) AS next_pos')
                          .where('relative_position >= ?', relative_position)
                          .order(:relative_position)

  find_next_gap(items_with_next_pos, range.last)
end

#find_next_gap_beforeObject



143
144
145
146
147
148
149
150
# File 'lib/gitlab/relative_positioning/item_context.rb', line 143

def find_next_gap_before
  items_with_next_pos = scoped_items
                          .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position DESC) AS next_pos')
                          .where('relative_position <= ?', relative_position)
                          .order(relative_position: :desc)

  find_next_gap(items_with_next_pos, range.first)
end

#grouping_columnObject



97
98
99
# File 'lib/gitlab/relative_positioning/item_context.rb', line 97

def grouping_column
  model_class.relative_positioning_parent_column
end

#lhs_neighbourObject



68
69
70
# File 'lib/gitlab/relative_positioning/item_context.rb', line 68

def lhs_neighbour
  neighbour(object.next_object_by_relative_position(ignoring: ignoring, order: :desc))
end

#max_relative_positionObject



33
34
35
# File 'lib/gitlab/relative_positioning/item_context.rb', line 33

def max_relative_position
  strong_memoize(:max_relative_position) { calculate_relative_position('MAX') }
end

#max_siblingObject



101
102
103
104
105
106
107
# File 'lib/gitlab/relative_positioning/item_context.rb', line 101

def max_sibling
  sib = relative_siblings
    .order(model_class.arel_table[:relative_position].desc.nulls_last)
    .first

  neighbour(sib)
end

#min_relative_positionObject



29
30
31
# File 'lib/gitlab/relative_positioning/item_context.rb', line 29

def min_relative_position
  strong_memoize(:min_relative_position) { calculate_relative_position('MIN') }
end

#min_siblingObject



109
110
111
112
113
114
115
# File 'lib/gitlab/relative_positioning/item_context.rb', line 109

def min_sibling
  sib = relative_siblings
    .order(model_class.arel_table[:relative_position].asc.nulls_last)
    .first

  neighbour(sib)
end

#neighbour(item) ⇒ Object



76
77
78
79
80
# File 'lib/gitlab/relative_positioning/item_context.rb', line 76

def neighbour(item)
  return unless item.present?

  self.class.new(item, range, ignoring: ignoring)
end

#next_relative_positionObject



41
42
43
# File 'lib/gitlab/relative_positioning/item_context.rb', line 41

def next_relative_position
  calculate_relative_position('MIN') { |r| nextify(r) } if object.relative_position
end

#nextify(relation, gt = true) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/gitlab/relative_positioning/item_context.rb', line 45

def nextify(relation, gt = true)
  if gt
    relation.where("relative_position > ?", relative_position)
  else
    relation.where("relative_position < ?", relative_position)
  end
end

#place_at_position(position, lhs) ⇒ Object

Handles the possibility that the position is already occupied by a sibling



58
59
60
61
62
63
64
65
66
# File 'lib/gitlab/relative_positioning/item_context.rb', line 58

def place_at_position(position, lhs)
  current_occupant = relative_siblings.find_by(relative_position: position)

  if current_occupant.present?
    Mover.new(position, range).move(object, lhs.object, current_occupant)
  else
    object.relative_position = position
  end
end

#positioned?Boolean

Returns:

  • (Boolean)


25
26
27
# File 'lib/gitlab/relative_positioning/item_context.rb', line 25

def positioned?
  relative_position.present?
end

#prev_relative_positionObject



37
38
39
# File 'lib/gitlab/relative_positioning/item_context.rb', line 37

def prev_relative_position
  calculate_relative_position('MAX') { |r| nextify(r, false) } if object.relative_position
end

#relative_positionObject



176
177
178
# File 'lib/gitlab/relative_positioning/item_context.rb', line 176

def relative_position
  object.relative_position
end

#relative_siblings(relation = scoped_items) ⇒ Object



53
54
55
# File 'lib/gitlab/relative_positioning/item_context.rb', line 53

def relative_siblings(relation = scoped_items)
  object.exclude_self(relation)
end

#rhs_neighbourObject



72
73
74
# File 'lib/gitlab/relative_positioning/item_context.rb', line 72

def rhs_neighbour
  neighbour(object.next_object_by_relative_position(ignoring: ignoring, order: :asc))
end

#scoped_itemsObject



172
173
174
# File 'lib/gitlab/relative_positioning/item_context.rb', line 172

def scoped_items
  object.relative_positioning_scoped_items(ignoring: ignoring)
end

#shift_leftObject



125
126
127
128
# File 'lib/gitlab/relative_positioning/item_context.rb', line 125

def shift_left
  move_sequence_before(true)
  object.reset_relative_position
end

#shift_rightObject



130
131
132
133
# File 'lib/gitlab/relative_positioning/item_context.rb', line 130

def shift_right
  move_sequence_after(true)
  object.reset_relative_position
end