░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/var/www/canvas/gems/acts_as_list/lib/active_record/acts
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: list.rb
# frozen_string_literal: true # # Copyright (C) 2011 - present Instructure, Inc. # # This file is part of Canvas. # # Canvas is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, version 3 of the License. # # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along # with this program. If not, see <http://www.gnu.org/licenses/>. module ActiveRecord module Acts # :nodoc: module List # :nodoc: def self.included(base) base.extend(ClassMethods) end # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. # The class that has this specified needs to have a +position+ column defined as an integer on # the mapped database table. # # Todo list example: # # class TodoList < ActiveRecord::Base # has_many :todo_items, :order => "position" # end # # class TodoItem < ActiveRecord::Base # belongs_to :todo_list # acts_as_list :scope => :todo_list # end # # todo_list.first.move_to_bottom # todo_list.last.insert_at(2) module ClassMethods # Configuration options are: # # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) # * +scope+ - restricts what is to be considered a list. Acceptable values are symbols, array of symbols, # or a hash of symbols to values. Pass self as the value to mean it must match the current record. # symbols that match associations are expanded to match the foreign key (polymorphic associations # are supported) def acts_as_list(options = {}) configuration = { column: "position" } configuration.update(options) if options.is_a?(Hash) if configuration[:scope] scope = configuration[:scope] # translate symbols and arrays to hash format scope = case scope when Symbol { scope => self } when Array scope.index_with { self } when Hash scope else raise ArgumentError, "scope must be nil, a symbol, an array, or a hash" end # expand assocations to their foreign keys new_scope = {} scope.each do |k, v| if (reflection = reflections[k.to_s]) key = reflection.foreign_key new_scope[key] = v if reflection.options[:polymorphic] key = reflection.foreign_type new_scope[key] = v end else new_scope[k] = v end end scope = new_scope # build the conditions hash, using literal values or the attribute if it's self conditions = scope.to_h { |k, v| [k, (v == self) ? k : v.inspect] } conditions = conditions.map { |c, v| "#{c}: #{v}" }.join(", ") # build the in_scope method, matching literals or requiring a foreign keys # to be non-nil in_scope_conditions = [] variable_conditions, constant_conditions = scope.partition { |_k, v| v == self } in_scope_conditions.concat(variable_conditions.map { |c, _v| "!#{c}.nil?" }) in_scope_conditions.concat(constant_conditions.map do |c, v| if v.is_a?(Array) "#{v.inspect}.include?(#{c})" else "#{c} == #{v.inspect}" end end) scope_condition_method = <<~RUBY def scope_condition { #{conditions} } end def in_scope? #{in_scope_conditions.join(" && ")} end def list_scope_base self.class.base_class.where(scope_condition) end RUBY else scope_condition_method = <<~RUBY def scope_condition nil end def in_scope? true end def list_scope_base self.class.base_class.all end RUBY end class_eval <<~RUBY, __FILE__, __LINE__ + 1 include ActiveRecord::Acts::List::InstanceMethods def self.position_column '#{configuration[:column]}' end #{scope_condition_method} def list_scope list_scope_base.order(self.class.position_column.to_sym, self.class.primary_key.to_sym) end before_destroy :remove_from_list_for_destroy before_create :add_to_list_bottom RUBY scope(:ordered, -> { order(position_column.to_sym, primary_key.to_sym) }) if position_column != "position" define_method(:position) { read_attribute(self.class.position_column.to_sym) } end end end # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works # by assuming the object to be the item in the list, so <tt>chapter.first?</tt> would return +true+ if # that chapter is the first in the list of all chapters. module InstanceMethods # Test if this record is in a list def in_list? !position.nil? && in_scope? end # Insert the item at the given position (defaults to the top position). def insert_at(position = :top) return unless in_scope? return move_to_top if position == :top current_position = self.position return true if in_list? && position == current_position transaction do if in_list? if position < current_position list_scope.where(self.class.position_column => position..(current_position - 1)) .update_all("#{self.class.position_column} = (#{self.class.position_column} + 1)") else list_scope.where(self.class.position_column => (current_position + 1)..position) .update_all("#{self.class.position_column} = (#{self.class.position_column} - 1)") end else list_scope.where("#{self.class.position_column}>=?", position) .update_all("#{self.class.position_column} = (#{self.class.position_column} + 1)") end update_attribute(self.class.position_column, position) end end # Move to the bottom of the list. If the item is already in the list, the items below it have their # position adjusted accordingly. def move_to_bottom return unless in_scope? transaction do bottom = bottom_position if in_list? insert_at(bottom) else update_attribute(self.class.position_column, bottom + 1) end end end # Move to the top of the list. If the item is already in the list, the items above it have their # position adjusted accordingly. def move_to_top return unless in_scope? transaction do top = top_position insert_at(top) end end # Removes the item from the list. def remove_from_list if in_list? transaction do list_scope.where("#{self.class.position_column}>?", position) .update_all("#{self.class.position_column} = (#{self.class.position_column} - 1)") update_attribute self.class.position_column, nil end end end # Return +true+ if this object is the first in the list. def first? return false unless in_list? position == top_position end # Return +true+ if this object is the last in the list. def last? return false unless in_list? position == bottom_position end # Returns the bottom position number in the list. # bottom_position # => 2 def bottom_position return nil unless in_scope? list_scope.maximum(self.class.position_column) end # Returns the bottom item def bottom_item return nil unless in_scope? list_scope.last end # Returns the top position number in the list. # top_position # => 1 def top_position return nil unless in_scope? list_scope.minimum(self.class.position_column) end # Returns the top item def top_item return nil unless in_scope? list_scope.first end # takes the given ids, and moves them to the beginning of the list. all other elements in the list # are moved downwards def update_order(ids) updates = [] done_ids = Set.new id_column = self.class.connection.quote_column_name(self.class.primary_key) ids.each do |id| id = id.to_i next unless id > 0 next if done_ids.include?(id) done_ids << id.to_i updates << "WHEN #{id_column}=#{id} THEN #{done_ids.length}" end return if updates.empty? transaction do done_ids = done_ids.to_a moving_positions = list_scope.where(self.class.primary_key => done_ids).pluck(self.class.position_column.to_sym) moving_positions.each_with_index do |position, index| next unless position updates << "WHEN #{self.class.position_column}<=#{position} THEN #{self.class.position_column}+#{moving_positions.length - index}" end list_scope.update_all("#{self.class.position_column}=CASE #{updates.join(" ")} ELSE position END") end end # fix conflicts in the list by reassigning all positions to be contiguous # the new order will preserve the current ordering based on (position, id) def fix_position_conflicts transaction do offset = 1 list_scope.select(self.class.primary_key).find_in_batches do |batch| updates = [] batch.each_with_index do |obj, index| updates << "WHEN #{obj.id} THEN #{index + offset}" end offset += batch.length list_scope.where(self.class.primary_key => batch) .update_all("#{self.class.position_column}=CASE #{self.class.connection.quote_column_name(self.class.primary_key)} #{updates.join(" ")} END") end end end # reassign positions to be contiguous, and begin at 1 alias_method :compact_list, :fix_position_conflicts # before_create callback def add_to_list_bottom return unless in_scope? return if in_list? self[self.class.position_column] = bottom_position.to_i + 1 end private def remove_from_list_for_destroy list_scope.where("#{self.class.position_column}>?", position) .update_all("#{self.class.position_column} = (#{self.class.position_column} - 1)") end end end end end
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 list.rb
RB
11.7 KB
2023-09-24 03:39
EDIT