diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2016-04-17 06:57:20 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2016-04-17 06:57:20 +0000 |
commit | 64afa24a7f72526a2cbf6761e51b6cd326aa0c36 (patch) | |
tree | e0766ba52e537838fb6c06c09e81b10010690b09 /lib | |
parent | f2eb979f66da758fbed7d98ae970f7ef74d1263f (diff) | |
download | redmine-64afa24a7f72526a2cbf6761e51b6cd326aa0c36.tar.gz redmine-64afa24a7f72526a2cbf6761e51b6cd326aa0c36.zip |
Replaces acts_as_list with an implementation that handles #position= (#12909).
Objects are reordered using the regular attribute writer #position= and AR callbacks.
git-svn-id: http://svn.redmine.org/redmine/trunk@15335 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'lib')
-rw-r--r-- | lib/redmine.rb | 2 | ||||
-rw-r--r-- | lib/redmine/acts/positioned.rb | 134 |
2 files changed, 136 insertions, 0 deletions
diff --git a/lib/redmine.rb b/lib/redmine.rb index 6d0d772ce..df234ebbd 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -28,6 +28,8 @@ rescue LoadError # Redcarpet is not available end +require 'redmine/acts/positioned' + require 'redmine/scm/base' require 'redmine/access_control' require 'redmine/access_keys' diff --git a/lib/redmine/acts/positioned.rb b/lib/redmine/acts/positioned.rb new file mode 100644 index 000000000..a322f1346 --- /dev/null +++ b/lib/redmine/acts/positioned.rb @@ -0,0 +1,134 @@ +# Redmine - project management software +# Copyright (C) 2006-2016 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module Redmine + module Acts + module Positioned + def self.included(base) + base.extend ClassMethods + end + + # This extension provides the capabilities for reordering objects in a list. + # The class needs to have a +position+ column defined as an integer on the + # mapped database table. + module ClassMethods + # Configuration options are: + # + # * +scope+ - restricts what is to be considered a list. Must be a symbol + # or an array of symbols + def acts_as_positioned(options = {}) + class_attribute :positioned_options + self.positioned_options = {:scope => Array(options[:scope])} + + send :include, Redmine::Acts::Positioned::InstanceMethods + + before_save :set_default_position + after_save :update_position + after_destroy :remove_position + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + end + + # Move to the given position + # For compatibility with the previous way of sorting items + def move_to=(pos) + case pos.to_s + when 'highest' + self.position = 1 + when 'higher' + self.position -= 1 if position > 1 + when 'lower' + self.position += 1 + when 'lowest' + self.position = nil + set_default_position + end + end + + private + + def position_scope + build_position_scope {|c| send(c)} + end + + def position_scope_was + build_position_scope {|c| send("#{c}_was")} + end + + def build_position_scope + condition_hash = self.class.positioned_options[:scope].inject({}) do |h, column| + h[column] = yield(column) + h + end + self.class.where(condition_hash) + end + + def set_default_position + if position.nil? + self.position = position_scope.maximum(:position).to_i + (new_record? ? 1 : 0) + end + end + + def update_position + if !new_record? && position_scope_changed? + remove_position + insert_position + elsif position_changed? + if position_was.nil? + insert_position + else + shift_positions + end + end + end + + def insert_position + position_scope.where("position >= ? AND id <> ?", position, id).update_all("position = position + 1") + end + + def remove_position + position_scope_was.where("position >= ? AND id <> ?", position_was, id).update_all("position = position - 1") + end + + def position_scope_changed? + (changed & self.class.positioned_options[:scope].map(&:to_s)).any? + end + + def shift_positions + offset = position_was <=> position + min, max = [position, position_was].sort + r = position_scope.where("id <> ? AND position BETWEEN ? AND ?", id, min, max).update_all("position = position + #{offset}") + if r != max - min + reset_positions_in_list + end + end + + def reset_positions_in_list + position_scope.reorder(:position, :id).pluck(:id).each_with_index do |record_id, p| + self.class.where(:id => record_id).update_all(:position => p+1) + end + end + end + end + end +end + +ActiveRecord::Base.send :include, Redmine::Acts::Positioned |