annotate .svn/pristine/a2/a265852a9b9824ef68e490e52d5427ad89e62e3e.svn-base @ 1524:82fac3dcf466 redmine-2.5-integration

Fix failure to interpret Javascript when autocompleting members for project
author Chris Cannam <chris.cannam@soundsoftware.ac.uk>
date Thu, 11 Sep 2014 10:24:38 +0100
parents dffacf8a6908
children
rev   line source
Chris@1517 1 module ActiveRecord
Chris@1517 2 module Acts #:nodoc:
Chris@1517 3 module List #:nodoc:
Chris@1517 4 def self.included(base)
Chris@1517 5 base.extend(ClassMethods)
Chris@1517 6 end
Chris@1517 7
Chris@1517 8 # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
Chris@1517 9 # The class that has this specified needs to have a +position+ column defined as an integer on
Chris@1517 10 # the mapped database table.
Chris@1517 11 #
Chris@1517 12 # Todo list example:
Chris@1517 13 #
Chris@1517 14 # class TodoList < ActiveRecord::Base
Chris@1517 15 # has_many :todo_items, :order => "position"
Chris@1517 16 # end
Chris@1517 17 #
Chris@1517 18 # class TodoItem < ActiveRecord::Base
Chris@1517 19 # belongs_to :todo_list
Chris@1517 20 # acts_as_list :scope => :todo_list
Chris@1517 21 # end
Chris@1517 22 #
Chris@1517 23 # todo_list.first.move_to_bottom
Chris@1517 24 # todo_list.last.move_higher
Chris@1517 25 module ClassMethods
Chris@1517 26 # Configuration options are:
Chris@1517 27 #
Chris@1517 28 # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
Chris@1517 29 # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
Chris@1517 30 # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
Chris@1517 31 # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
Chris@1517 32 # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
Chris@1517 33 def acts_as_list(options = {})
Chris@1517 34 configuration = { :column => "position", :scope => "1 = 1" }
Chris@1517 35 configuration.update(options) if options.is_a?(Hash)
Chris@1517 36
Chris@1517 37 configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
Chris@1517 38
Chris@1517 39 if configuration[:scope].is_a?(Symbol)
Chris@1517 40 scope_condition_method = %(
Chris@1517 41 def scope_condition
Chris@1517 42 if #{configuration[:scope].to_s}.nil?
Chris@1517 43 "#{configuration[:scope].to_s} IS NULL"
Chris@1517 44 else
Chris@1517 45 "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
Chris@1517 46 end
Chris@1517 47 end
Chris@1517 48 )
Chris@1517 49 else
Chris@1517 50 scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
Chris@1517 51 end
Chris@1517 52
Chris@1517 53 class_eval <<-EOV
Chris@1517 54 include ActiveRecord::Acts::List::InstanceMethods
Chris@1517 55
Chris@1517 56 def acts_as_list_class
Chris@1517 57 ::#{self.name}
Chris@1517 58 end
Chris@1517 59
Chris@1517 60 def position_column
Chris@1517 61 '#{configuration[:column]}'
Chris@1517 62 end
Chris@1517 63
Chris@1517 64 #{scope_condition_method}
Chris@1517 65
Chris@1517 66 before_destroy :remove_from_list
Chris@1517 67 before_create :add_to_list_bottom
Chris@1517 68 EOV
Chris@1517 69 end
Chris@1517 70 end
Chris@1517 71
Chris@1517 72 # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
Chris@1517 73 # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
Chris@1517 74 # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
Chris@1517 75 # the first in the list of all chapters.
Chris@1517 76 module InstanceMethods
Chris@1517 77 # Insert the item at the given position (defaults to the top position of 1).
Chris@1517 78 def insert_at(position = 1)
Chris@1517 79 insert_at_position(position)
Chris@1517 80 end
Chris@1517 81
Chris@1517 82 # Swap positions with the next lower item, if one exists.
Chris@1517 83 def move_lower
Chris@1517 84 return unless lower_item
Chris@1517 85
Chris@1517 86 acts_as_list_class.transaction do
Chris@1517 87 lower_item.decrement_position
Chris@1517 88 increment_position
Chris@1517 89 end
Chris@1517 90 end
Chris@1517 91
Chris@1517 92 # Swap positions with the next higher item, if one exists.
Chris@1517 93 def move_higher
Chris@1517 94 return unless higher_item
Chris@1517 95
Chris@1517 96 acts_as_list_class.transaction do
Chris@1517 97 higher_item.increment_position
Chris@1517 98 decrement_position
Chris@1517 99 end
Chris@1517 100 end
Chris@1517 101
Chris@1517 102 # Move to the bottom of the list. If the item is already in the list, the items below it have their
Chris@1517 103 # position adjusted accordingly.
Chris@1517 104 def move_to_bottom
Chris@1517 105 return unless in_list?
Chris@1517 106 acts_as_list_class.transaction do
Chris@1517 107 decrement_positions_on_lower_items
Chris@1517 108 assume_bottom_position
Chris@1517 109 end
Chris@1517 110 end
Chris@1517 111
Chris@1517 112 # Move to the top of the list. If the item is already in the list, the items above it have their
Chris@1517 113 # position adjusted accordingly.
Chris@1517 114 def move_to_top
Chris@1517 115 return unless in_list?
Chris@1517 116 acts_as_list_class.transaction do
Chris@1517 117 increment_positions_on_higher_items
Chris@1517 118 assume_top_position
Chris@1517 119 end
Chris@1517 120 end
Chris@1517 121
Chris@1517 122 # Move to the given position
Chris@1517 123 def move_to=(pos)
Chris@1517 124 case pos.to_s
Chris@1517 125 when 'highest'
Chris@1517 126 move_to_top
Chris@1517 127 when 'higher'
Chris@1517 128 move_higher
Chris@1517 129 when 'lower'
Chris@1517 130 move_lower
Chris@1517 131 when 'lowest'
Chris@1517 132 move_to_bottom
Chris@1517 133 end
Chris@1517 134 reset_positions_in_list
Chris@1517 135 end
Chris@1517 136
Chris@1517 137 def reset_positions_in_list
Chris@1517 138 acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i|
Chris@1517 139 unless item.send(position_column) == (i + 1)
Chris@1517 140 acts_as_list_class.where({:id => item.id}).
Chris@1517 141 update_all({position_column => (i + 1)})
Chris@1517 142 end
Chris@1517 143 end
Chris@1517 144 end
Chris@1517 145
Chris@1517 146 # Removes the item from the list.
Chris@1517 147 def remove_from_list
Chris@1517 148 if in_list?
Chris@1517 149 decrement_positions_on_lower_items
Chris@1517 150 update_attribute position_column, nil
Chris@1517 151 end
Chris@1517 152 end
Chris@1517 153
Chris@1517 154 # Increase the position of this item without adjusting the rest of the list.
Chris@1517 155 def increment_position
Chris@1517 156 return unless in_list?
Chris@1517 157 update_attribute position_column, self.send(position_column).to_i + 1
Chris@1517 158 end
Chris@1517 159
Chris@1517 160 # Decrease the position of this item without adjusting the rest of the list.
Chris@1517 161 def decrement_position
Chris@1517 162 return unless in_list?
Chris@1517 163 update_attribute position_column, self.send(position_column).to_i - 1
Chris@1517 164 end
Chris@1517 165
Chris@1517 166 # Return +true+ if this object is the first in the list.
Chris@1517 167 def first?
Chris@1517 168 return false unless in_list?
Chris@1517 169 self.send(position_column) == 1
Chris@1517 170 end
Chris@1517 171
Chris@1517 172 # Return +true+ if this object is the last in the list.
Chris@1517 173 def last?
Chris@1517 174 return false unless in_list?
Chris@1517 175 self.send(position_column) == bottom_position_in_list
Chris@1517 176 end
Chris@1517 177
Chris@1517 178 # Return the next higher item in the list.
Chris@1517 179 def higher_item
Chris@1517 180 return nil unless in_list?
Chris@1517 181 acts_as_list_class.where(
Chris@1517 182 "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
Chris@1517 183 ).first
Chris@1517 184 end
Chris@1517 185
Chris@1517 186 # Return the next lower item in the list.
Chris@1517 187 def lower_item
Chris@1517 188 return nil unless in_list?
Chris@1517 189 acts_as_list_class.where(
Chris@1517 190 "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
Chris@1517 191 ).first
Chris@1517 192 end
Chris@1517 193
Chris@1517 194 # Test if this record is in a list
Chris@1517 195 def in_list?
Chris@1517 196 !send(position_column).nil?
Chris@1517 197 end
Chris@1517 198
Chris@1517 199 private
Chris@1517 200 def add_to_list_top
Chris@1517 201 increment_positions_on_all_items
Chris@1517 202 end
Chris@1517 203
Chris@1517 204 def add_to_list_bottom
Chris@1517 205 self[position_column] = bottom_position_in_list.to_i + 1
Chris@1517 206 end
Chris@1517 207
Chris@1517 208 # Overwrite this method to define the scope of the list changes
Chris@1517 209 def scope_condition() "1" end
Chris@1517 210
Chris@1517 211 # Returns the bottom position number in the list.
Chris@1517 212 # bottom_position_in_list # => 2
Chris@1517 213 def bottom_position_in_list(except = nil)
Chris@1517 214 item = bottom_item(except)
Chris@1517 215 item ? item.send(position_column) : 0
Chris@1517 216 end
Chris@1517 217
Chris@1517 218 # Returns the bottom item
Chris@1517 219 def bottom_item(except = nil)
Chris@1517 220 conditions = scope_condition
Chris@1517 221 conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
Chris@1517 222 acts_as_list_class.where(conditions).reorder("#{position_column} DESC").first
Chris@1517 223 end
Chris@1517 224
Chris@1517 225 # Forces item to assume the bottom position in the list.
Chris@1517 226 def assume_bottom_position
Chris@1517 227 update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
Chris@1517 228 end
Chris@1517 229
Chris@1517 230 # Forces item to assume the top position in the list.
Chris@1517 231 def assume_top_position
Chris@1517 232 update_attribute(position_column, 1)
Chris@1517 233 end
Chris@1517 234
Chris@1517 235 # This has the effect of moving all the higher items up one.
Chris@1517 236 def decrement_positions_on_higher_items(position)
Chris@1517 237 acts_as_list_class.
Chris@1517 238 where("#{scope_condition} AND #{position_column} <= #{position}").
Chris@1517 239 update_all("#{position_column} = (#{position_column} - 1)")
Chris@1517 240 end
Chris@1517 241
Chris@1517 242 # This has the effect of moving all the lower items up one.
Chris@1517 243 def decrement_positions_on_lower_items
Chris@1517 244 return unless in_list?
Chris@1517 245 acts_as_list_class.
Chris@1517 246 where("#{scope_condition} AND #{position_column} > #{send(position_column).to_i}").
Chris@1517 247 update_all("#{position_column} = (#{position_column} - 1)")
Chris@1517 248 end
Chris@1517 249
Chris@1517 250 # This has the effect of moving all the higher items down one.
Chris@1517 251 def increment_positions_on_higher_items
Chris@1517 252 return unless in_list?
Chris@1517 253 acts_as_list_class.
Chris@1517 254 where("#{scope_condition} AND #{position_column} < #{send(position_column).to_i}").
Chris@1517 255 update_all("#{position_column} = (#{position_column} + 1)")
Chris@1517 256 end
Chris@1517 257
Chris@1517 258 # This has the effect of moving all the lower items down one.
Chris@1517 259 def increment_positions_on_lower_items(position)
Chris@1517 260 acts_as_list_class.
Chris@1517 261 where("#{scope_condition} AND #{position_column} >= #{position}").
Chris@1517 262 update_all("#{position_column} = (#{position_column} + 1)")
Chris@1517 263 end
Chris@1517 264
Chris@1517 265 # Increments position (<tt>position_column</tt>) of all items in the list.
Chris@1517 266 def increment_positions_on_all_items
Chris@1517 267 acts_as_list_class.
Chris@1517 268 where("#{scope_condition}").
Chris@1517 269 update_all("#{position_column} = (#{position_column} + 1)")
Chris@1517 270 end
Chris@1517 271
Chris@1517 272 def insert_at_position(position)
Chris@1517 273 remove_from_list
Chris@1517 274 increment_positions_on_lower_items(position)
Chris@1517 275 self.update_attribute(position_column, position)
Chris@1517 276 end
Chris@1517 277 end
Chris@1517 278 end
Chris@1517 279 end
Chris@1517 280 end