summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2016-04-17 06:57:20 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2016-04-17 06:57:20 +0000
commit64afa24a7f72526a2cbf6761e51b6cd326aa0c36 (patch)
treee0766ba52e537838fb6c06c09e81b10010690b09 /lib
parentf2eb979f66da758fbed7d98ae970f7ef74d1263f (diff)
downloadredmine-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.rb2
-rw-r--r--lib/redmine/acts/positioned.rb134
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