Module: RelativePositioning

Extended by:
ActiveSupport::Concern
Includes:
Gitlab::RelativePositioning
Included in:
DesignManagement::Design, Issue, WorkItems::ParentLink
Defined in:
app/models/concerns/relative_positioning.rb

Overview

This module makes it possible to handle items as a list, where the order of items can be easily altered Requirements:

The model must have the following named columns:

- id: integer
- relative_position: integer

The model must support a concept of siblings via a child->parent relationship, to enable rebalancing and ‘GROUP BY` in queries.

  • example: project -> issues, project is the parent relation (issues table has a parent_id column)

Two class methods must be defined when including this concern:

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

Constant Summary

Constants included from Gitlab::RelativePositioning

Gitlab::RelativePositioning::IDEAL_DISTANCE, Gitlab::RelativePositioning::IllegalRange, Gitlab::RelativePositioning::InvalidPosition, Gitlab::RelativePositioning::IssuePositioningDisabled, Gitlab::RelativePositioning::MAX_GAP, Gitlab::RelativePositioning::MAX_POSITION, Gitlab::RelativePositioning::MIN_GAP, Gitlab::RelativePositioning::MIN_POSITION, Gitlab::RelativePositioning::NoSpaceLeft, Gitlab::RelativePositioning::START_POSITION, Gitlab::RelativePositioning::STEPS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::RelativePositioning

range

Class Method Details

.moverObject



124
125
126
# File 'app/models/concerns/relative_positioning.rb', line 124

def self.mover
  ::Gitlab::RelativePositioning::Mover.new(START_POSITION, (MIN_POSITION..MAX_POSITION))
end

Instance Method Details

#check_repositioning_allowed!Object

To be overriden on child classes whenever blocking position updates is necessary.



130
131
132
# File 'app/models/concerns/relative_positioning.rb', line 130

def check_repositioning_allowed!
  nil
end

#could_not_move(exception) ⇒ Object

Override if you want to be notified of failures to move



204
205
# File 'app/models/concerns/relative_positioning.rb', line 204

def could_not_move(exception)
end

#exclude_self(relation, excluded: self) ⇒ Object

This method is used to exclude the current self (or another object) from a relation. Customize this if ‘id <> :id` is not sufficient



199
200
201
# File 'app/models/concerns/relative_positioning.rb', line 199

def exclude_self(relation, excluded: self)
  relation.id_not_in(excluded.id)
end

#model_classObject

Override if the model class needs a more complicated computation (e.g. the object is a member of a union).



215
216
217
# File 'app/models/concerns/relative_positioning.rb', line 215

def model_class
  self.class
end

#move_after(before = self) ⇒ Object



143
144
145
146
147
148
# File 'app/models/concerns/relative_positioning.rb', line 143

def move_after(before = self)
  RelativePositioning.mover.move(self, before, nil)
rescue NoSpaceLeft => e
  could_not_move(e)
  raise e
end

#move_before(after = self) ⇒ Object



150
151
152
153
154
155
# File 'app/models/concerns/relative_positioning.rb', line 150

def move_before(after = self)
  RelativePositioning.mover.move(self, nil, after)
rescue NoSpaceLeft => e
  could_not_move(e)
  raise e
end

#move_between(before, after) ⇒ Object



134
135
136
137
138
139
140
141
# File 'app/models/concerns/relative_positioning.rb', line 134

def move_between(before, after)
  before, after = [before, after].sort_by(&:relative_position) if before && after

  RelativePositioning.mover.move(self, before, after)
rescue NoSpaceLeft => e
  could_not_move(e)
  raise e
end

#move_to_endObject



157
158
159
160
161
162
# File 'app/models/concerns/relative_positioning.rb', line 157

def move_to_end
  RelativePositioning.mover.move_to_end(self)
rescue NoSpaceLeft => e
  could_not_move(e)
  self.relative_position = MAX_POSITION
end

#move_to_startObject



164
165
166
167
168
169
# File 'app/models/concerns/relative_positioning.rb', line 164

def move_to_start
  RelativePositioning.mover.move_to_start(self)
rescue NoSpaceLeft => e
  could_not_move(e)
  self.relative_position = MIN_POSITION
end

#next_object_by_relative_position(ignoring: nil, order: :asc) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/concerns/relative_positioning.rb', line 171

def next_object_by_relative_position(ignoring: nil, order: :asc)
  relation = relative_positioning_scoped_items(ignoring: ignoring).reorder(relative_position: order)

  relation = if order == :asc
               relation.where(self.class.arel_table[:relative_position].gt(relative_position))
             else
               relation.where(self.class.arel_table[:relative_position].lt(relative_position))
             end

  relation.first
end

#relative_positioning_scoped_items(ignoring: nil) ⇒ Object



183
184
185
186
187
# File 'app/models/concerns/relative_positioning.rb', line 183

def relative_positioning_scoped_items(ignoring: nil)
  relation = self.class.relative_positioning_query_base(self)
  relation = exclude_self(relation, excluded: ignoring) if ignoring.present?
  relation
end

#reset_relative_positionObject

Override if the implementing class is not a simple application record, for example if the record is loaded from a union.



209
210
211
# File 'app/models/concerns/relative_positioning.rb', line 209

def reset_relative_position
  reset.relative_position
end

#update_relative_siblings(relation, range, delta) ⇒ Object

This method is used during rebalancing - override it to customise the update logic:



191
192
193
194
195
# File 'app/models/concerns/relative_positioning.rb', line 191

def update_relative_siblings(relation, range, delta)
  relation
    .where(relative_position: range)
    .update_all("relative_position = relative_position + #{delta}")
end