├── .gitignore ├── .rspec ├── Gemfile ├── README.markdown ├── Rakefile ├── VERSION ├── acts_as_list_mongoid.gemspec ├── example └── example.rb ├── init.rb ├── lib ├── acts_as_list_mongoid.rb ├── init.rb └── mongoid │ └── acts_as_list.rb ├── model ├── embedded_item.rb └── referenced_category.rb ├── mongoid.yml └── spec ├── .rspec ├── acts_as_list ├── embedded_item_spec.rb └── referenced_category_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg 3 | .bundle 4 | vendor 5 | Gemfile.lock 6 | */.DS_Store 7 | /coverage/ 8 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color --format nested -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'mongoid', '>= 3.0.0' 4 | gem "mongoid_embedded_helper", '>= 0.2.5' 5 | 6 | group :test, :development do 7 | gem "cutter", ">= 0" 8 | gem "shoulda", ">= 0" 9 | gem "rspec", ">= 2.5" 10 | end 11 | 12 | group :development do 13 | gem "bundler", ">= 1.0.10" 14 | gem "jeweler", ">= 1.6.4" 15 | gem "simplecov", ">= 0" 16 | end 17 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Mongoid Acts as list 2 | 3 | This is a port of the classic +acts_as_list+ to Mongoid. 4 | 5 | This *acts_as* extension provides the capabilities for sorting and reordering a number of objects in a list. 6 | If you do not specify custom position +column+ in the options, a key named +pos+ will be used automatically. 7 | 8 | ## Installation 9 | 10 | gem install acts_as_list_mongoid 11 | 12 | ## Usage 13 | 14 | See the /specs folder specs that demontrate the API. Usage examples are located in the /examples folder. 15 | 16 | ## Update 26, Nov 2010 17 | 18 | The gem doesn't seem to work with the latest versions of Mongoid (> beta14), please help fix this ;) 19 | Usage has been simplified using a suggestion by 'KieranP' 20 | 21 | To make a class Act as List, simply do: 22 | 23 |
 24 |   include ActsAsList::Mongoid   
 25 | 
26 | 27 | And it will automatically set up a field and call acts_as_list with that field. By default the field name is :position. 28 | You can change the defaut position_column name used: ActsAsList::Mongoid.default_position_column = :pos. 29 | For this class variable to be effetive, it should be set before calling include ActsAsList::Mongoid. 30 | 31 | ## Example 32 | 33 |
 34 |   require 'mongoid'
 35 |   require 'mongoid_embedded_helper'
 36 | 
 37 |   Mongoid.configure.master = Mongo::Connection.new.db('acts_as_list-test')
 38 | 
 39 |   class Item
 40 |     include Mongoid::Document
 41 |     include Mongoid::Timestamps
 42 |     include ActsAsList::Mongoid 
 43 |     
 44 |     field :number, :type => Integer
 45 |     
 46 |     embedded_in :list, :inverse_of => :items
 47 |   end    
 48 | 
 49 |   class List
 50 |     include Mongoid::Document
 51 |     field :name, :type => String
 52 |     embeds_many :items
 53 |   end
 54 | 
 55 | 
 56 |   todo_list = List.new :name => 'My todo list'
 57 | 
 58 |   %w{'clean', 'wash', 'repair'}.each do |name| 
 59 |     todo_item = Item.new(:name => name)
 60 |     todo_list.items << todo_item
 61 |   end  
 62 |   todo_list.items.init_list! # IMPORTANT!!!
 63 | 
 64 |   todo_list.items.first.move(:bottom)
 65 |   todo_list.items.last.move(:higher)
 66 | 
67 | 68 | ## Overriding defaults 69 | 70 | By default, when including ActsAsList::Mongoid, the field is set to :pos and the acts_as_list column to :pos. 71 | To change this: 72 | 73 |
 74 |   include ActsAsList::Mongoid   
 75 |   
 76 |   field :pos, :type => Integer
 77 |   acts_as_list :column => :pos
 78 | 
79 | 80 | 81 | ### List initialization 82 | 83 | In order for the list items to be initialized properly, it is necessary to call the method init_list! on the 84 | collection in order for the position of each list item to be set to an initial position. 85 | 86 | +Example:+ 87 | todo_list.items.init_list! 88 | 89 | ## New move API borrowed from Data Mapper *in-list* plugin 90 | 91 |
 92 | item.move(:highest)          # moves to top of list.
 93 | item.move(:lowest)           # moves to bottom of list.
 94 | item.move(:top)              # moves to top of list.
 95 | item.move(:bottom)           # moves to bottom of list.
 96 | item.move(:up)               # moves one up (:higher and :up is the same) within the scope.
 97 | item.move(:down)             # moves one up (:lower and :down is the same) within the scope.
 98 | item.move(:to => position)   # moves item to a specific position.
 99 | item.move(:above => other)   # moves item above the other item.*
100 | item.move(:below => other)
101 | 
102 | 
103 | ## Running the specs
104 | 
105 | rspec spec
106 | 
107 | 
108 | 


--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
 1 | # encoding: utf-8
 2 | 
 3 | require 'rubygems'
 4 | require 'bundler'
 5 | begin
 6 |   Bundler.setup(:default, :development)
 7 | rescue Bundler::BundlerError => e
 8 |   $stderr.puts e.message
 9 |   $stderr.puts "Run `bundle install` to install missing gems"
10 |   exit e.status_code
11 | end
12 | require 'rake'
13 | 
14 | require 'jeweler'
15 | Jeweler::Tasks.new do |gem|
16 |   # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17 |     gem.name        = "acts_as_list_mongoid"
18 |     gem.summary     = %Q{acts_as_list for Mongoid}
19 |     gem.description = %Q{Make your Mongoid model acts as a list. This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list.
20 |       The instances that take part in the list should have a +position+ field of type Integer.}
21 |     gem.email       = "kmandrup@gmail.com"
22 |     gem.homepage    = "http://github.com/rails/acts_as_list"
23 |     gem.authors     = ["Kristian Mandrup"]
24 |   # dependencies defined in Gemfile
25 | end
26 | Jeweler::RubygemsDotOrgTasks.new
27 | 
28 | require 'rspec/core/rake_task'
29 | 
30 | RSpec::Core::RakeTask.new(:spec)
31 | 
32 | task :default => :spec
33 | 
34 | desc "Run RSpec with code coverage"
35 | task :coverage do
36 |   ENV['COVERAGE'] = "true"
37 |   Rake::Task[:spec].execute
38 | end
39 | 
40 | 
41 | require 'rdoc/task'
42 | Rake::RDocTask.new do |rdoc|
43 |   version = File.exist?('VERSION') ? File.read('VERSION') : ""
44 | 
45 |   rdoc.rdoc_dir = 'rdoc'
46 |   rdoc.title = "acts_as_list_mongoid #{version}"
47 |   rdoc.rdoc_files.include('README*')
48 |   rdoc.rdoc_files.include('lib/**/*.rb')
49 | end
50 | 
51 | 
52 | 
53 | 


--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 0.3.0
2 | 


--------------------------------------------------------------------------------
/acts_as_list_mongoid.gemspec:
--------------------------------------------------------------------------------
 1 | # Generated by jeweler
 2 | # DO NOT EDIT THIS FILE DIRECTLY
 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
 4 | # -*- encoding: utf-8 -*-
 5 | 
 6 | Gem::Specification.new do |s|
 7 |   s.name = "acts_as_list_mongoid"
 8 |   s.version = "0.3.0"
 9 | 
10 |   s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 |   s.authors = ["Kristian Mandrup"]
12 |   s.date = "2012-07-10"
13 |   s.description = "Make your Mongoid model acts as a list. This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list.\n      The instances that take part in the list should have a +position+ field of type Integer."
14 |   s.email = "kmandrup@gmail.com"
15 |   s.extra_rdoc_files = [
16 |     "README.markdown"
17 |   ]
18 |   s.files = [
19 |     ".rspec",
20 |     "Gemfile",
21 |     "README.markdown",
22 |     "Rakefile",
23 |     "VERSION",
24 |     "acts_as_list_mongoid.gemspec",
25 |     "example/example.rb",
26 |     "init.rb",
27 |     "lib/acts_as_list_mongoid.rb",
28 |     "lib/init.rb",
29 |     "lib/mongoid/acts_as_list.rb",
30 |     "model/embedded_item.rb",
31 |     "model/referenced_category.rb",
32 |     "spec/.rspec",
33 |     "spec/acts_as_list/embedded_item_spec.rb",
34 |     "spec/acts_as_list/referenced_category_spec.rb",
35 |     "spec/spec_helper.rb"
36 |   ]
37 |   s.homepage = "http://github.com/rails/acts_as_list"
38 |   s.require_paths = ["lib"]
39 |   s.rubygems_version = "1.8.24"
40 |   s.summary = "acts_as_list for Mongoid"
41 | 
42 |   if s.respond_to? :specification_version then
43 |     s.specification_version = 3
44 | 
45 |     if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46 |       s.add_runtime_dependency(%q, [">= 3.0.0"])
47 |       s.add_runtime_dependency(%q, [">= 0.2.5"])
48 |       s.add_development_dependency(%q, [">= 0"])
49 |       s.add_development_dependency(%q, [">= 0"])
50 |       s.add_development_dependency(%q, [">= 2.5"])
51 |       s.add_development_dependency(%q, [">= 1.0.10"])
52 |       s.add_development_dependency(%q, [">= 1.6.4"])
53 |       s.add_development_dependency(%q, [">= 0"])
54 |     else
55 |       s.add_dependency(%q, [">= 3.0.0"])
56 |       s.add_dependency(%q, [">= 0.2.5"])
57 |       s.add_dependency(%q, [">= 0"])
58 |       s.add_dependency(%q, [">= 0"])
59 |       s.add_dependency(%q, [">= 2.5"])
60 |       s.add_dependency(%q, [">= 1.0.10"])
61 |       s.add_dependency(%q, [">= 1.6.4"])
62 |       s.add_dependency(%q, [">= 0"])
63 |     end
64 |   else
65 |     s.add_dependency(%q, [">= 3.0.0"])
66 |     s.add_dependency(%q, [">= 0.2.5"])
67 |     s.add_dependency(%q, [">= 0"])
68 |     s.add_dependency(%q, [">= 0"])
69 |     s.add_dependency(%q, [">= 2.5"])
70 |     s.add_dependency(%q, [">= 1.0.10"])
71 |     s.add_dependency(%q, [">= 1.6.4"])
72 |     s.add_dependency(%q, [">= 0"])
73 |   end
74 | end
75 | 
76 | 


--------------------------------------------------------------------------------
/example/example.rb:
--------------------------------------------------------------------------------
 1 | require 'mongoid'
 2 | require 'mongoid_embedded_helper'
 3 |                  
 4 | Mongoid.configure.master = Mongo::Connection.new.db('acts_as_list-test')
 5 | 
 6 | class Item
 7 |   include Mongoid::Document
 8 |   include Mongoid::Timestamps
 9 |   include ActsAsList::Mongoid 
10 |   
11 |   field :pos, :type => Integer
12 |   field :number, :type => Integer
13 |   
14 |   acts_as_list :column => :pos
15 | 
16 |   embedded_in :list, :inverse_of => :items
17 | end    
18 | 
19 | class List
20 |   include Mongoid::Document
21 |   field :name, :type => String
22 |   embeds_many :items
23 | end
24 | 
25 | 
26 | todo_list = List.new :name => 'My todo list'
27 | 
28 | %w{'clean', 'wash', 'repair'}.each do |name| 
29 |   todo_item = Item.new(:name => name)
30 |   todo_list.items << todo_item
31 | end  
32 | todo_list.items.created!
33 | 
34 | todo_list.items.first.move_to_bottom
35 | todo_list.items.last.move_higher
36 | 


--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | $:.unshift "#{File.dirname(__FILE__)}/lib"
2 | require 'acts_as_list/mongo_mapper/rails3'
3 | 


--------------------------------------------------------------------------------
/lib/acts_as_list_mongoid.rb:
--------------------------------------------------------------------------------
1 | require "mongoid"
2 | require 'mongoid/acts_as_list'
3 | 
4 | class Module
5 |   def act_as_list
6 |     self.send :include, ActsAsList::Mongoid
7 |   end
8 | end
9 | 


--------------------------------------------------------------------------------
/lib/init.rb:
--------------------------------------------------------------------------------
1 | require 'acts_as_list_mongoid'


--------------------------------------------------------------------------------
/lib/mongoid/acts_as_list.rb:
--------------------------------------------------------------------------------
  1 | require "mongoid"
  2 | require 'mongoid_embedded_helper'
  3 | require 'mongoid_adjust'
  4 | 
  5 | module ActsAsList
  6 |   module Mongoid
  7 |     class << self
  8 |       attr_accessor :default_position_column
  9 |     end
 10 | 
 11 |     def self.included(klass)
 12 |       klass.extend InitializerMethods
 13 |       key = self.default_position_column || :position
 14 |       klass.field key, :type => Integer
 15 |       klass.acts_as_list :column => key.to_s
 16 |     end
 17 | 
 18 |     module InitializerMethods
 19 |       def acts_as_list(options = {})
 20 |         configuration = { :column => 'position' }
 21 |         configuration.update(options) if options.is_a?(Hash)
 22 |         
 23 |         if configuration[:scope]
 24 |           configuration[:scope] = configuration[:scope].to_sym
 25 |           configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].to_s !~ /_id$/
 26 |         end
 27 |         
 28 |         define_method :position_column do
 29 |           configuration[:column].to_s
 30 |         end
 31 | 
 32 |         if !configuration[:scope]
 33 |           define_method :scope_condition do
 34 |             {position_key.ne => nil}
 35 |           end
 36 |         elsif configuration[:scope].is_a?(Symbol)
 37 |           define_method :scope_condition do
 38 |             {configuration[:scope] => self[configuration[:scope]]}
 39 |           end
 40 |         else
 41 |           raise ArgumentError, "acts_as_list must either take a valid :scope option or be in an embedded document and use the parent document as scope"
 42 |         end
 43 | 
 44 |         include ::Mongoid::EmbeddedHelper
 45 |         include InstanceMethods
 46 |         include Fields
 47 |         include Triggers
 48 |         extend Fields
 49 |         extend ClassMethods
 50 |       end
 51 |     end
 52 | 
 53 |     module ClassMethods
 54 |       def in_scope
 55 |         where(scope_condition)
 56 |       end
 57 | 
 58 |       def move_commands symbol
 59 |         case symbol
 60 |         when :symbol
 61 |           [:highest, :top, :lowest, :bottom, :up, :higher, :down, :lower]
 62 |         when :hash
 63 |           [:to, :above, :below]
 64 |         else
 65 |           raise ArgumentError, "no move_commands defined for: #{symbol}"
 66 |         end
 67 |       end
 68 |     end
 69 | 
 70 |     module InstanceMethods
 71 |       def move command
 72 |         if command.kind_of? Symbol
 73 |           case command
 74 |           when :highest, :top
 75 |             move_to_top
 76 |           when :lowest, :bottom
 77 |             move_to_bottom
 78 |           when :up, :higher
 79 |             move_higher
 80 |           when :down, :lower
 81 |             move_lower
 82 |           else
 83 |             raise ArgumentError, "unknown move command '#{command}', try one of #{self.class.move_commands_available}"
 84 |           end
 85 |         elsif command.kind_of? Hash
 86 |           other = command.values.first
 87 |           cmd = command.keys.first
 88 |           case cmd
 89 |           when :to
 90 |             move_to(other)
 91 |           when :above
 92 |             move_above(other)
 93 |           when :below
 94 |             move_below(other)
 95 |           else
 96 |             raise ArgumentError, "Hash command #{cmd.inspect} not valid, must be one of"
 97 |           end
 98 |         else
 99 |           raise ArgumentError, "move command takes either a Symbol or Hash as an argument, not a #{command.class}"
100 |         end
101 |       end
102 | 
103 | 
104 |       def order_by_position conditions, extras = []
105 |         sub_collection = in_collection.where(conditions)
106 |         sub_collection = if embedded?
107 |           if sub_collection.method(:sort).arity == 0
108 |             sub_collection.sort { |x,y| x.my_position <=> y.my_position }
109 |           else
110 |             sub_collection.sort(:my_position => 1)
111 |           end
112 |         else
113 |           sub_collection.order_by(position_key.to_sym.asc)
114 |         end
115 | 
116 |         if !extras.empty?
117 |           sub_collection = if embedded?
118 |             sub_collection.sort do |x,y|
119 |               if x.my_position == y.my_position
120 |                 x.created_at <=> y.created_at
121 |               else
122 |                x.my_position <=> y.my_position
123 |               end
124 |             end
125 |           else
126 |             sub_collection.order_by(extras)
127 |           end
128 |         end
129 | 
130 |         sub_collection
131 |       end
132 | 
133 |       # conditions, { position_column => 1 }
134 |       def do_decrement( conditions, options)
135 |         in_collection.where(conditions).adjust! position_key => -1
136 |       end
137 | 
138 |       def do_increment( conditions, options)
139 |         in_collection.where(conditions).adjust! position_key => 1
140 |       end
141 | 
142 |       def less_than_me
143 |         { position_key.lt => my_position.to_i}
144 |       end
145 | 
146 |       def greater_than_me
147 |         { position_key.gt => my_position.to_i}
148 |       end
149 | 
150 |       def insert_at(position = 1)
151 |         insert_in_list_at(position)
152 |       end
153 | 
154 |       def move_to(position = 1)
155 |         insert_in_list_at(position)
156 |       end
157 | 
158 |       def move_below(object)
159 |         new_pos = (self == object) ? self.my_position : ((object.my_position > self.my_position) ? object.my_position : object.my_position + 1)
160 |         move_to(new_pos)
161 |       end
162 | 
163 |       def move_above(object)
164 |         new_pos = ( self == object ) ? self.my_position : ((object.my_position > self.my_position) ? object.my_position - 1 : object.my_position)
165 |         move_to(new_pos)
166 |       end
167 | 
168 |       # Insert the item at the given position (defaults to the top position of 1).
169 |       def insert_in_list_at(position = 1)
170 |         insert_at_position(position)
171 |       end
172 | 
173 |       # Swap positions with the next lower item, if one exists.
174 |       def move_lower
175 |         low_item = lower_item
176 |         return unless low_item
177 | 
178 |         low_item.decrement_position
179 |         increment_position
180 |       end
181 | 
182 |       # Swap positions with the next higher item, if one exists.
183 |       def move_higher
184 |         high_item = higher_item
185 |         return unless high_item
186 | 
187 |         high_item.increment_position
188 |         decrement_position
189 |       end
190 | 
191 |       # Move to the bottom of the list. If the item is already in the list, the items below it have their
192 |       # position adjusted accordingly.
193 |       def move_to_bottom
194 |         return unless in_list?
195 | 
196 |         decrement_positions_on_lower_items
197 |         assume_bottom_position 
198 |       end
199 | 
200 |       # Move to the top of the list. If the item is already in the list, the items above it have their
201 |       # position adjusted accordingly.
202 |       def move_to_top
203 |         return unless in_list?
204 | 
205 |         increment_positions_on_higher_items
206 |         assume_top_position
207 |       end
208 | 
209 |       # Removes the item from the list.
210 |       def remove_from_list
211 |         if in_list?
212 |           decrement_positions_on_lower_items
213 |           set_my_position nil
214 |         end
215 |       end
216 | 
217 |       # Increase the position of this item without adjusting the rest of the list.
218 |       def increment_position
219 |         return unless in_list?
220 |         # in_collection.where(:pos => my_position).
221 |         adjust!(position_key => 1) 
222 |         save!
223 |       end
224 | 
225 |       # Decrease the position of this item without adjusting the rest of the list.
226 |       def decrement_position
227 |         return unless in_list?
228 | 
229 |         # in_collection.where(:pos => my_position).
230 |         adjust!(position_key => -1)
231 |         save!
232 |       end
233 | 
234 |       # Return +true+ if this object is the first in the list.
235 |       def first?
236 |         return false unless in_list?
237 |         my_position == 1
238 |       end
239 | 
240 |       # Return +true+ if this object is the last in the list.
241 |       def last?
242 |         return false unless in_list?
243 |         bottom_pos = bottom_position_in_list
244 |         my_position == bottom_pos
245 |       end
246 | 
247 |       # Return the next higher item in the list.
248 |       def higher_item
249 |         return nil unless in_list?
250 |         conditions = scope_condition.merge!( less_than_me )
251 |         order_by_position(conditions).last
252 |       end
253 | 
254 |       # Return the next lower item in the list.
255 |       def lower_item
256 |         return nil unless in_list?
257 | 
258 |         conditions = scope_condition.merge!( greater_than_me )
259 |         order_by_position(conditions).first
260 |       end
261 | 
262 |       # Test if this record is in a list
263 |       def in_list?
264 |         !my_position.nil?
265 |       end
266 | 
267 |       # sorts all items in the list
268 |       # if two items have same position, the one created more recently goes first
269 |       def sort
270 |         conditions = scope_condition
271 |         list_items = order_by_position(conditions, :created_at.desc).to_a
272 | 
273 |         list_items.each_with_index do |list_item, index|
274 |           list_item.set_my_position index + 1
275 |         end
276 |       end
277 | 
278 |       private
279 | 
280 |       def add_to_list_top
281 |         increment_positions_on_all_items
282 |       end
283 | 
284 |       def add_to_list_bottom
285 |         bottom_pos = bottom_position_in_list.to_i
286 |         set_my_position(bottom_pos + 1)
287 |       end
288 | 
289 |       # Overwrite this method to define the scope of the list changes
290 |       def scope_condition
291 |         {}
292 |       end
293 | 
294 |       # Returns the bottom position number in the list.
295 |       #   bottom_position_in_list    # => 2
296 |       def bottom_position_in_list(except = nil)
297 |         item = bottom_item #(except)
298 |         item ? item.my_position : 0
299 |       end
300 | 
301 |       # Returns the bottom item
302 |       def bottom_item(except = nil)
303 |         conditions = scope_condition
304 |         if except
305 |           conditions.merge!( { position_key.ne => except.my_position } )
306 |         end
307 | 
308 |         order_by_position(conditions).last
309 |       end
310 | 
311 |       # Forces item to assume the bottom position in the list.
312 |       def assume_bottom_position
313 |         pos = bottom_position_in_list(self).to_i + 1 
314 |         set_my_position(pos)
315 |       end
316 | 
317 |       # Forces item to assume the top position in the list.
318 |       def assume_top_position
319 |         set_my_position(1)
320 |       end
321 | 
322 |       # This has the effect of moving all the higher items up one.
323 |       def decrement_positions_on_higher_items(position)
324 |         conditions = scope_condition
325 |         conditions.merge!( { position_key.lt => position } )
326 | 
327 |         decrease_all! in_collection.where(conditions)
328 |       end
329 | 
330 |       # This has the effect of moving all the lower items up one.
331 |       def decrement_positions_on_lower_items(max_pos = nil)
332 |         return unless in_list?
333 |         conditions = scope_condition
334 |         conditions.merge!( greater_than_me )
335 |         conditions.merge!({ position_key.lt => max_pos} ) if max_pos
336 | 
337 |         decrease_all! in_collection.where(conditions)
338 |       end
339 | 
340 |       # This has the effect of moving all the higher items down one.
341 |       def increment_positions_on_higher_items(min_pos = nil)
342 |         return unless in_list?
343 |         conditions = scope_condition
344 |         conditions.merge!( less_than_me )
345 |         conditions.merge!({ position_key.gt => min_pos} ) if min_pos
346 | 
347 |         increase_all! in_collection.where(conditions)
348 |       end
349 | 
350 |       def adjust_all! collection, number
351 |         collection.adjust!(position_key => number).each{|doc| doc.save}
352 |       end
353 | 
354 |       def increase_all! collection
355 |         adjust_all! collection, 1
356 |       end
357 | 
358 |       def decrease_all! collection
359 |         adjust_all! collection, -1
360 |       end
361 | 
362 |       # This has the effect of moving all the lower items down one.
363 |       def increment_positions_on_lower_items(position)
364 |         conditions = scope_condition
365 |         conditions.merge!( { position_key.gte => position } )
366 | 
367 |         increase_all! in_collection.where(conditions)
368 |       end
369 | 
370 |       # Increments position (position_column) of all items in the list.
371 |       def increment_positions_on_all_items
372 |         conditions = scope_condition
373 |         increase_all! in_collection.where(conditions)
374 |       end
375 | 
376 |       def insert_at_position(position)
377 |         position = [position, 1].max
378 |         remove_from_list
379 |         increment_positions_on_lower_items(position)
380 |         set_my_position(position)
381 |       end
382 |     end
383 | 
384 |     module Triggers
385 |       def after_parentize
386 |         # should register on root element to be called when root is saved first time!?
387 |       end
388 | 
389 |       def init_list_item!
390 |         self['created_at'] = Time.now
391 |         self['updated_at'] = Time.now
392 |         add_to_list_bottom unless in_list?
393 |       end
394 | 
395 |     end
396 | 
397 |     module Fields
398 |       def my_position
399 |         self[position_column]
400 |       end
401 | 
402 |       def set_my_position new_position
403 |         if new_position != my_position
404 |           self.update_attributes! position_column => new_position
405 |         end
406 |       end
407 | 
408 |       def [](field_name)
409 |         self.send field_name
410 |       end
411 | 
412 |       def []=(key, value)
413 |         @attributes[key.to_s] = value
414 |         save!
415 |       end
416 | 
417 |       def ==(other)
418 |         return true if other.equal?(self)
419 |         return true if other.instance_of?(self.class) and other.respond_to?('_id') and other._id == self._id
420 |         false
421 |       end
422 | 
423 |       # Overwrites default comparer to return comparison index value based on defined position
424 |       def <=>(other)
425 |         self[position_column] <=> other[position_column]
426 |       end
427 | 
428 |       def position_key
429 |         position_column.to_sym
430 |       end
431 |     end
432 |   end
433 | end
434 | 
435 | class Array
436 |   def init_list!
437 |     each {|i| i.init_list_item! }
438 |   end
439 | end
440 | 


--------------------------------------------------------------------------------
/model/embedded_item.rb:
--------------------------------------------------------------------------------
 1 | class Item
 2 |   include Mongoid::Document
 3 |   include Mongoid::Timestamps
 4 |   # include ActsAsList::Mongoid 
 5 |   # field :number, :type => Integer
 6 |   act_as_list
 7 | 
 8 |   embedded_in :list, :inverse_of => :items
 9 | end
10 | 
11 | class List
12 |   include Mongoid::Document
13 |   field :name, :type => String
14 |   embeds_many :items
15 | end
16 | 


--------------------------------------------------------------------------------
/model/referenced_category.rb:
--------------------------------------------------------------------------------
 1 | class Category
 2 |   include Mongoid::Document
 3 |   include Mongoid::Timestamps
 4 |   include ActsAsList::Mongoid
 5 | 
 6 |   field :number, :type => Integer
 7 | 
 8 |   # field :pos, :type => Integer
 9 |   # acts_as_list :column => :pos
10 | 
11 |   #many should below in, or we will get:
12 |   #NoMethodError:
13 |   #       undefined method `entries' for #
14 |   belongs_to :category
15 |   has_many :categories
16 | 
17 |   def scope_condition
18 |     {:category_id => category.id, :pos.ne => nil}
19 |   end
20 | end
21 | 


--------------------------------------------------------------------------------
/mongoid.yml:
--------------------------------------------------------------------------------
 1 | test:
 2 |   sessions:
 3 |     default:
 4 |       hosts:
 5 |       - localhost:27017
 6 |       database: acts_as_list_test
 7 | 
 8 | 
 9 | 
10 | 


--------------------------------------------------------------------------------
/spec/.rspec:
--------------------------------------------------------------------------------
1 | --format nested --color


--------------------------------------------------------------------------------
/spec/acts_as_list/embedded_item_spec.rb:
--------------------------------------------------------------------------------
  1 | require 'spec_helper'
  2 | 
  3 | ActsAsList::Mongoid.default_position_column = :pos
  4 | require 'embedded_item'
  5 | 
  6 | describe 'ActsAsList for Mongoid' do
  7 | 
  8 |   before :each do
  9 |     @list = List.create!
 10 |     @list.items = []
 11 |     (1..4).each do |counter|
 12 |       @list.items << Item.new(:number => counter)
 13 |     end
 14 |     @list.save!
 15 | 
 16 |     @list.items.init_list!
 17 |   end
 18 | 
 19 |   after :each do
 20 |     List.mongo_session.drop
 21 |   end
 22 | 
 23 |   def get_positions list
 24 |     list.items.sort.map(&:number)
 25 |   end
 26 | 
 27 |   context "4 list items (1,2,3,4) that have parent_id pointing to first list container"  do
 28 |     describe '# initial configuration' do
 29 |       it "should list items 1 to 4 in order" do
 30 |         positions = get_positions @list 
 31 |         positions.should == [1, 2, 3, 4]
 32 |       end
 33 |     end
 34 | 
 35 |     describe '#reordering' do
 36 |       it "should move item 2 to position 3" do
 37 |         @list.items[1].increment_position
 38 |         @list.items[2].decrement_position
 39 |         get_positions(@list).should == [1, 3, 2, 4]
 40 |       end
 41 | 
 42 | 
 43 |       it "should move item 2 to position 3" do
 44 |         @list.items.where(:number => 2).first.move_lower
 45 |         get_positions(@list).should == [1, 3, 2, 4]
 46 |       end
 47 | 
 48 |       it "move :down should move item 2 to position 3" do
 49 |         @list.items.where(:number => 2).first.move(:down)
 50 |         get_positions(@list).should == [1, 3, 2, 4]
 51 |       end
 52 | 
 53 |       it "move :lower should move item 2 to position 3" do
 54 |         @list.items.where(:number => 2).first.move(:lower)
 55 |         get_positions(@list).should == [1, 3, 2, 4]
 56 |       end
 57 | 
 58 |       it "should move item 2 to position 1" do
 59 |         @list.items.where(:number => 2).first.move_higher
 60 |         get_positions(@list).should == [2, 1, 3, 4]
 61 |       end
 62 | 
 63 |       it "move :up should move item 2 to position 1" do
 64 |         @list.items.where(:number => 2).first.move(:up)
 65 |         get_positions(@list).should == [2, 1, 3, 4]
 66 |       end
 67 | 
 68 |       it "move :higher should move item 2 to position 1" do
 69 |         @list.items.where(:number => 2).first.move(:higher)
 70 |         get_positions(@list).should == [2, 1, 3, 4]
 71 |       end
 72 | 
 73 |       it "should move item 1 to bottom" do
 74 |         @list.items.where(:number => 1).first.move_to_bottom
 75 |         get_positions(@list).should == [2, 3, 4, 1]
 76 |       end
 77 | 
 78 |       it "move :lowest should move item 1 to bottom" do
 79 |         @list.items.where(:number => 1).first.move(:lowest)
 80 |         get_positions(@list).should == [2, 3, 4, 1]
 81 |       end
 82 | 
 83 |       it "should move item 1 to top" do
 84 |         @list.items.where(:number => 1).first.move_to_top
 85 |         get_positions(@list).should == [1, 2, 3, 4]
 86 |       end
 87 | 
 88 |       it "move :highest should move item 1 to top" do
 89 |         @list.items.where(:number => 1).first.move(:highest)
 90 |         get_positions(@list).should == [1, 2, 3, 4]
 91 |       end
 92 | 
 93 |       it "move :unknown should cause argument error" do
 94 |         lambda {@list.items.where(:number => 1).first.move(:unknown)}.should raise_error
 95 |       end
 96 | 
 97 |       it "should move item 2 to bottom" do
 98 |         @list.items.where(:number => 2).first.move_to_bottom
 99 |         get_positions(@list).should == [1, 3, 4, 2]
100 |       end
101 | 
102 |       it "should move item 4 to top" do
103 |         @list.items.where(:number => 4).first.move_to_top
104 |         get_positions(@list).should == [4, 1, 2, 3]
105 |       end
106 | 
107 |       it "should move item 3 to bottom" do
108 |         @list.items.where(:number => 3).first.move_to_bottom
109 |         get_positions(@list).should == [1, 2, 4, 3]
110 |       end
111 | 
112 |       it "items[2].move_to(4) should move item 2 to position 4" do
113 |         @list.items.where(:number => 2).first.move_to(4)
114 |         get_positions(@list).should == [1, 3, 4, 2]
115 |       end
116 | 
117 |       it "items[2].insert_at(3) should move item 2 to position 3" do
118 |         @list.items.where(:number => 2).first.insert_at(3)
119 |         get_positions(@list).should == [1, 3, 2, 4]
120 |       end
121 | 
122 |       it "items[2].move(:to => 3) should move item 2 to position 3" do
123 |         @list.items.where(:number => 2).first.move(:to => 3)
124 |         get_positions(@list).should == [1, 3, 2, 4]
125 |       end
126 | 
127 |       it "items[1].move_below(item[2]) should move item 1 to position 2" do
128 |         item2 = @list.items.where(:number => 2).first
129 |         @list.items.where(:number => 1).first.move_below(item2)
130 |         get_positions(@list).should == [2, 1, 3, 4]
131 |       end
132 | 
133 |       it "items[3].move_below(item[1]) should move item 3 to position 2" do
134 |         item1 = @list.items.where(:number => 1).first
135 |         @list.items.where(:number => 3).first.move_below(item1)
136 |         get_positions(@list).should == [1, 3, 2, 4]
137 |       end
138 | 
139 |       it "items[3].move_above(item[2]) should move item 3 to position 2" do
140 |         item2 = @list.items.where(:number => 2).first
141 |         @list.items.where(:number => 3).first.move_above(item2)
142 |         get_positions(@list).should == [1, 3, 2, 4]
143 |       end
144 | 
145 |       it "items[1].move_above(item[3]) should move item 1 to position 2" do
146 |         item3 = @list.items.where(:number => 3).first
147 |         @list.items.where(:number => 1).first.move_above(item3)
148 |         get_positions(@list).should == [2, 1, 3, 4]
149 |       end
150 | 
151 |     end
152 | 
153 |     describe 'relative position queries' do
154 |       it "should find item 2 to be lower item of item 1" do
155 |         expected = @list.items.where(:pos => 2).first
156 |         @list.items.where(:pos => 1).first.lower_item.should == expected
157 |       end
158 | 
159 |       it "should not find any item higher than nr 1" do
160 |         @list.items.where(:pos => 1).first.higher_item.should == nil
161 |       end
162 | 
163 |       it "should find item 3 to be higher item of item 4" do
164 |         expected = @list.items.where(:pos => 3).first
165 |         @list.items.where(:pos => 4).first.higher_item.should == expected
166 |       end
167 | 
168 |       it "should not find item lower than item 4" do
169 |         @list.items.where(:pos => 4).first.lower_item.should == nil
170 |       end
171 |     end
172 |   end
173 | end
174 | 


--------------------------------------------------------------------------------
/spec/acts_as_list/referenced_category_spec.rb:
--------------------------------------------------------------------------------
  1 | require 'spec_helper'
  2 | 
  3 | ActsAsList::Mongoid.default_position_column = :pos
  4 | require 'referenced_category'
  5 | 
  6 | describe 'ActsAsList for Mongoid' do
  7 | 
  8 |   before :each do
  9 |     @category = Category.create!
 10 |     @category.categories = []
 11 |     (1..4).each do |counter|
 12 |       @category.categories << Category.new(:number => counter)
 13 |     end
 14 |     @category.save!
 15 | 
 16 |     @category.categories.init_list!
 17 |   end
 18 | 
 19 |   after :each do
 20 |     Category.mongo_session.drop
 21 |   end
 22 | 
 23 |   def get_positions(category)
 24 |     category.reload.categories.sort.map(&:number)
 25 |   end
 26 | 
 27 |   context "4 category categories (1,2,3,4) that have parent_id pointing to first category container" do
 28 |     describe '# initial configuration' do
 29 |       it "should category categories 1 to 4 in order" do
 30 |         positions = get_positions @category
 31 |         positions.should == [1, 2, 3, 4]
 32 |       end
 33 |     end
 34 | 
 35 |     describe '#reordering' do
 36 |       it "should move item 2 to position 3" do
 37 |         @category.categories[1].increment_position
 38 |         @category.categories[2].decrement_position
 39 |         get_positions(@category).should == [1, 3, 2, 4]
 40 |       end
 41 | 
 42 | 
 43 |       it "should move item 2 to position 3 directly" do
 44 |         Category.where(:number => 2).first.move_lower
 45 |         get_positions(@category).should == [1, 3, 2, 4]
 46 |       end
 47 | 
 48 |       it "move :down should move item 2 to position 3" do
 49 |         Category.where(:number => 2).first.move(:down)
 50 |         get_positions(@category).should == [1, 3, 2, 4]
 51 |       end
 52 | 
 53 |       it "move :lower should move item 2 to position 3" do
 54 |         Category.where(:number => 2).first.move(:lower)
 55 |         get_positions(@category).should == [1, 3, 2, 4]
 56 |       end
 57 | 
 58 |       it "should move item 2 to position 1" do
 59 |         Category.where(:number => 2).first.move_higher
 60 |         get_positions(@category).should == [2, 1, 3, 4]
 61 |       end
 62 | 
 63 |       it "move :up should move item 2 to position 1" do
 64 |         Category.where(:number => 2).first.move(:up)
 65 |         get_positions(@category).should == [2, 1, 3, 4]
 66 |       end
 67 | 
 68 |       it "move :higher should move item 2 to position 1" do
 69 |         Category.where(:number => 2).first.move(:higher)
 70 |         get_positions(@category).should == [2, 1, 3, 4]
 71 |       end
 72 | 
 73 |       it "should move item 1 to bottom" do
 74 |         Category.where(:number => 1).first.move_to_bottom
 75 |         get_positions(@category).should == [2, 3, 4, 1]
 76 |       end
 77 | 
 78 |       it "move :lowest should move item 1 to bottom" do
 79 |         Category.where(:number => 1).first.move(:lowest)
 80 |         get_positions(@category).should == [2, 3, 4, 1]
 81 |       end
 82 | 
 83 |       it "should move item 1 to top" do
 84 |         Category.where(:number => 1).first.move_to_top
 85 |         get_positions(@category).should == [1, 2, 3, 4]
 86 |       end
 87 | 
 88 |       it "move :highest should move item 1 to top" do
 89 |         Category.where(:number => 1).first.move(:highest)
 90 |         get_positions(@category).should == [1, 2, 3, 4]
 91 |       end
 92 | 
 93 |       it "move :unknown should cause argument error" do
 94 |         lambda {Category.where(:number => 1).first.move(:unknown)}.should raise_error
 95 |       end
 96 | 
 97 |       it "should move item 2 to bottom" do
 98 |         Category.where(:number => 2).first.move_to_bottom
 99 |         get_positions(@category).should == [1, 3, 4, 2]
100 |       end
101 | 
102 |       it "should move item 4 to top" do
103 |         Category.where(:number => 4).first.move_to_top
104 |         get_positions(@category).should == [4, 1, 2, 3]
105 |       end
106 | 
107 |       it "should move item 3 to bottom" do
108 |         Category.where(:number => 3).first.move_to_bottom
109 |         get_positions(@category).should == [1, 2, 4, 3]
110 |       end
111 | 
112 |       it "categories[2].move_to(4) should move item 2 to position 4" do
113 |         Category.where(:number => 2).first.move_to(4)
114 |         get_positions(@category).should == [1, 3, 4, 2]
115 |       end
116 | 
117 |       it "categories[2].insert_at(3) should move item 2 to position 3" do
118 |         Category.where(:number => 2).first.insert_at(3)
119 |         get_positions(@category).should == [1, 3, 2, 4]
120 |       end
121 | 
122 |       it "categories[2].move(:to => 3) should move item 2 to position 3" do
123 |         Category.where(:number => 2).first.move(:to => 3)
124 |         get_positions(@category).should == [1, 3, 2, 4]
125 |       end
126 | 
127 |       it "categories[1].move_below(item[2]) should move item 1 to position 2" do
128 |         item2 = Category.where(:number => 2).first
129 |         Category.where(:number => 1).first.move_below(item2)
130 |         get_positions(@category).should == [2, 1, 3, 4]
131 |       end
132 | 
133 |       it "categories[3].move_below(item[1]) should move item 3 to position 2" do
134 |         item1 = Category.where(:number => 1).first
135 |         Category.where(:number => 3).first.move_below(item1)
136 |         get_positions(@category).should == [1, 3, 2, 4]
137 |       end
138 | 
139 |       it "categories[3].move_above(item[2]) should move item 3 to position 2" do
140 |         item2 = Category.where(:number => 2).first
141 |         Category.where(:number => 3).first.move_above(item2)
142 |         get_positions(@category).should == [1, 3, 2, 4]
143 |       end
144 | 
145 |       it "categories[1].move_above(item[3]) should move item 1 to position 2" do
146 |         item3 = Category.where(:number => 3).first
147 |         Category.where(:number => 1).first.move_above(item3)
148 |         get_positions(@category).should == [2, 1, 3, 4]
149 |       end
150 | 
151 |     end
152 | 
153 |     describe 'relative position queries' do
154 |       it "should find item 2 to be lower item of item 1" do
155 |         expected = Category.where(:pos => 2).first
156 |         Category.where(:pos => 1).first.lower_item.should == expected
157 |       end
158 | 
159 |       it "should not find any item higher than nr 1" do
160 |         Category.where(:pos => 1).first.higher_item.should == nil
161 |       end
162 | 
163 |       it "should find item 3 to be higher item of item 4" do
164 |         expected = Category.where(:pos => 3).first
165 |         Category.where(:pos => 4).first.higher_item.should == expected
166 |       end
167 | 
168 |       it "should not find item lower than item 4" do
169 |         Category.where(:pos => 4).first.lower_item.should == nil
170 |       end
171 |     end
172 |   end
173 | end
174 | 


--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
 1 | if ENV['COVERAGE']
 2 |   require 'simplecov'
 3 |   SimpleCov.start
 4 | end
 5 | 
 6 | require 'rspec'
 7 | require 'rspec/autorun'
 8 | require 'mongoid'
 9 | require 'mongoid_embedded_helper'
10 | # require 'mongoid_adjust'
11 | require 'acts_as_list_mongoid'
12 | 
13 | 
14 | $:.unshift "#{File.dirname(__FILE__)}/../model/"
15 | ENV["MONGOID_ENV"]="test"
16 | Mongoid.load! "#{File.dirname(__FILE__)}/../mongoid.yml"
17 | 
18 | 
19 | 


--------------------------------------------------------------------------------