acts_as_list_mongoid 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.DS_Store ADDED
Binary file
data/README.markdown ADDED
@@ -0,0 +1,61 @@
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
+ <code>gem install acts_a_list_mongoid</code>
11
+
12
+ ## Usage
13
+
14
+ See the /specs folder specs that demontrate the API. Usage examples are located in the /examples folder.
15
+
16
+ ## Example
17
+
18
+ <pre>
19
+ require 'mongoid'
20
+ require 'mongoid_embedded_helper'
21
+
22
+ Mongoid.configure.master = Mongo::Connection.new.db('acts_as_list-test')
23
+
24
+ class Item
25
+ include Mongoid::Document
26
+ include Mongoid::Timestamps
27
+ include ActsAsList::Mongoid
28
+
29
+ field :pos, :type => Integer
30
+ field :number, :type => Integer
31
+
32
+ acts_as_list :column => :pos
33
+
34
+ embedded_in :list, :inverse_of => :items
35
+ end
36
+
37
+ class List
38
+ include Mongoid::Document
39
+ field :name, :type => String
40
+ embeds_many :items
41
+ end
42
+
43
+
44
+ todo_list = List.new :name => 'My todo list'
45
+
46
+ %w{'clean', 'wash', 'repair'}.each do |name|
47
+ todo_item = Item.new(:name => name)
48
+ todo_list.items << todo_item
49
+ end
50
+ todo_list.items.created! # IMPORTANT!!!
51
+
52
+ todo_list.items.first.move_to_bottom
53
+ todo_list.items.last.move_higher
54
+ </pre>
55
+
56
+
57
+ ## Running the specs
58
+
59
+ <code>rspec spec</code>
60
+
61
+
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "acts_as_list_mongoid"
8
+ gem.summary = %Q{Gem version of acts_as_list for Mongoid}
9
+ 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.
10
+ The instances that take part in the list should have a +position+ field of type Integer.}
11
+ gem.email = "kmandrup@gmail.com"
12
+ gem.homepage = "http://github.com/rails/acts_as_list"
13
+ gem.authors = ["Kristian Mandrup"]
14
+ gem.add_dependency "mongoid", ">= 2.0.0.beta7"
15
+ gem.add_dependency "mongoid_embedded_helper", ">= 0.2.3"
16
+ # gem.add_development_dependency "yard"
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,35 @@
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
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'acts_as_list/mongo_mapper/rails3'
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,2 @@
1
+ require "mongoid"
2
+ require 'mongoid/acts_as_list'
data/lib/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_list_mongoid'
@@ -0,0 +1,370 @@
1
+ require "mongoid"
2
+ require 'mongoid_embedded_helper'
3
+
4
+ module ActsAsList
5
+ module Mongoid
6
+ def self.included(model)
7
+ model.class_eval do
8
+ extend InitializerMethods
9
+ end
10
+ end
11
+
12
+ module InitializerMethods
13
+ def acts_as_list(options = {})
14
+ configuration = { :column => 'position' }
15
+ configuration.update(options) if options.is_a?(Hash)
16
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
17
+
18
+ # write_inheritable_attribute :acts_as_list_options, configuration
19
+ # class_inheritable_reader :acts_as_list_options
20
+
21
+ define_method :position_column do
22
+ configuration[:column].to_s
23
+ end
24
+
25
+ if !configuration[:scope]
26
+ define_method :scope_condition do
27
+ {position_key.ne => nil}
28
+ end
29
+ elsif configuration[:scope].is_a?(Symbol)
30
+ define_method :scope_condition do
31
+ { "#{configuration[:scope].to_s}" => "\#{#{configuration[:scope].to_s}}".to_i }.symbolize_keys!
32
+ end
33
+ else
34
+ 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"
35
+ end
36
+
37
+ include ::Mongoid::EmbeddedHelper
38
+ include InstanceMethods
39
+ include Fields
40
+ include Triggers
41
+ extend Fields
42
+ extend ClassMethods
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ def in_scope
48
+ where(scope_condition)
49
+ end
50
+ end
51
+
52
+ module InstanceMethods
53
+ def order_by_position conditions, extras = []
54
+ sub_collection = in_collection.where(conditions)
55
+ sub_collection = if embedded?
56
+ sub_collection.sort { |x,y| x.my_position <=> y.my_position }
57
+ else
58
+ sub_collection.order_by([position_key, :desc])
59
+ end
60
+
61
+ if !extras.empty?
62
+ sub_collection = if embedded?
63
+ sub_collection.sort do |x,y|
64
+ if x.my_position == y.my_position
65
+ x.created_at <=> y.created_at
66
+ else
67
+ x.my_position <=> y.my_position
68
+ end
69
+ end
70
+ else
71
+ sub_collection.order_by(extras)
72
+ end
73
+ end
74
+
75
+ sub_collection
76
+ end
77
+
78
+ # conditions, { position_column => 1 }
79
+ def do_decrement( conditions, options)
80
+ in_collection.where(conditions).adjust! position_key => -1
81
+ end
82
+
83
+ def do_increment( conditions, options)
84
+ in_collection.where(conditions).adjust! position_key => 1
85
+ end
86
+
87
+ def less_than_me
88
+ { position_key.lt => my_position.to_i}
89
+ end
90
+
91
+ def greater_than_me
92
+ { position_key.gt => my_position.to_i}
93
+ end
94
+
95
+ def insert_at(position = 1)
96
+ insert_in_list_at(position)
97
+ end
98
+
99
+ # Insert the item at the given position (defaults to the top position of 1).
100
+ def insert_in_list_at(position = 1)
101
+ insert_at_position(position)
102
+ end
103
+
104
+ # Swap positions with the next lower item, if one exists.
105
+ def move_lower
106
+ low_item = lower_item
107
+ return unless low_item
108
+
109
+ low_item.decrement_position
110
+ increment_position
111
+ end
112
+
113
+ # Swap positions with the next higher item, if one exists.
114
+ def move_higher
115
+ high_item = higher_item
116
+ return unless high_item
117
+
118
+ high_item.increment_position
119
+ decrement_position
120
+ end
121
+
122
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
123
+ # position adjusted accordingly.
124
+ def move_to_bottom
125
+ return unless in_list?
126
+
127
+ decrement_positions_on_lower_items
128
+ assume_bottom_position
129
+ end
130
+
131
+ # Move to the top of the list. If the item is already in the list, the items above it have their
132
+ # position adjusted accordingly.
133
+ def move_to_top
134
+ return unless in_list?
135
+
136
+ increment_positions_on_higher_items
137
+ assume_top_position
138
+ end
139
+
140
+ # Removes the item from the list.
141
+ def remove_from_list
142
+ if in_list?
143
+ decrement_positions_on_lower_items
144
+ set_my_position nil
145
+ end
146
+ end
147
+
148
+ # Increase the position of this item without adjusting the rest of the list.
149
+ def increment_position
150
+ return unless in_list?
151
+ # in_collection.where(:pos => my_position).
152
+ adjust!(position_key => 1)
153
+ save!
154
+ end
155
+
156
+ # Decrease the position of this item without adjusting the rest of the list.
157
+ def decrement_position
158
+ return unless in_list?
159
+
160
+ # in_collection.where(:pos => my_position).
161
+ adjust!(position_key => -1)
162
+ save!
163
+ end
164
+
165
+ # Return +true+ if this object is the first in the list.
166
+ def first?
167
+ return false unless in_list?
168
+ my_position == 1
169
+ end
170
+
171
+ # Return +true+ if this object is the last in the list.
172
+ def last?
173
+ return false unless in_list?
174
+ bottom_pos = bottom_position_in_list
175
+ my_position == bottom_pos
176
+ end
177
+
178
+ # Return the next higher item in the list.
179
+ def higher_item
180
+ return nil unless in_list?
181
+ conditions = scope_condition.merge!( less_than_me )
182
+
183
+ order_by_position(conditions).last
184
+ end
185
+
186
+ # Return the next lower item in the list.
187
+ def lower_item
188
+ return nil unless in_list?
189
+
190
+ conditions = scope_condition.merge!( greater_than_me )
191
+
192
+ order_by_position(conditions).first
193
+ end
194
+
195
+ # Test if this record is in a list
196
+ def in_list?
197
+ !my_position.nil?
198
+ end
199
+
200
+ # sorts all items in the list
201
+ # if two items have same position, the one created more recently goes first
202
+ def sort
203
+ conditions = scope_condition
204
+
205
+ list_items = order_by_position(conditions, :created_at.desc).to_a
206
+
207
+ list_items.each_with_index do |list_item, index|
208
+ list_item.set_my_position index + 1
209
+ end
210
+ end
211
+
212
+ private
213
+
214
+ def add_to_list_top
215
+ increment_positions_on_all_items
216
+ end
217
+
218
+ def add_to_list_bottom
219
+ bottom_pos = bottom_position_in_list.to_i
220
+ set_my_position(bottom_pos + 1)
221
+ end
222
+
223
+ # Overwrite this method to define the scope of the list changes
224
+ def scope_condition
225
+ {}
226
+ end
227
+
228
+ # Returns the bottom position number in the list.
229
+ # bottom_position_in_list # => 2
230
+ def bottom_position_in_list(except = nil)
231
+ item = bottom_item #(except)
232
+ item ? item.my_position : 0
233
+ end
234
+
235
+ # Returns the bottom item
236
+ def bottom_item(except = nil)
237
+ conditions = scope_condition
238
+ if except
239
+ conditions.merge!( { position_key.ne => except.my_position } )
240
+ end
241
+
242
+ order_by_position(conditions).last
243
+ end
244
+
245
+ # Forces item to assume the bottom position in the list.
246
+ def assume_bottom_position
247
+ pos = bottom_position_in_list(self).to_i + 1
248
+ set_my_position(pos)
249
+ end
250
+
251
+ # Forces item to assume the top position in the list.
252
+ def assume_top_position
253
+ set_my_position(1)
254
+ end
255
+
256
+ # This has the effect of moving all the higher items up one.
257
+ def decrement_positions_on_higher_items(position)
258
+ conditions = scope_condition
259
+ conditions.merge!( { position_key.lt => position } )
260
+ in_collection.where(conditions).adjust! position_key => 1
261
+ end
262
+
263
+ # This has the effect of moving all the lower items up one.
264
+ def decrement_positions_on_lower_items
265
+ return unless in_list?
266
+ conditions = scope_condition
267
+ conditions.merge!( greater_than_me )
268
+
269
+ decrease_all! in_collection.where(conditions)
270
+ end
271
+
272
+ # This has the effect of moving all the higher items down one.
273
+ def increment_positions_on_higher_items
274
+ return unless in_list?
275
+ conditions = scope_condition
276
+ conditions.merge!( less_than_me )
277
+
278
+ increase_all! in_collection.where(conditions)
279
+ end
280
+
281
+ def adjust_all! collection, number
282
+ collection.adjust! position_key => number
283
+ end
284
+
285
+ def increase_all! collection
286
+ adjust_all! collection, 1
287
+ end
288
+
289
+ def decrease_all! collection
290
+ adjust_all! collection, -1
291
+ end
292
+
293
+ # This has the effect of moving all the lower items down one.
294
+ def increment_positions_on_lower_items(position)
295
+ conditions = scope_condition
296
+ conditions.merge!( { position_key.gte => position } )
297
+
298
+ increase_all! in_collection.where(conditions)
299
+ end
300
+
301
+ # Increments position (<tt>position_column</tt>) of all items in the list.
302
+ def increment_positions_on_all_items
303
+ conditions = scope_condition
304
+
305
+ increase_all! in_collection.where(conditions)
306
+ end
307
+
308
+ def insert_at_position(position)
309
+ remove_from_list
310
+ increment_positions_on_lower_items(position)
311
+ set_my_position(position)
312
+ end
313
+ end
314
+
315
+ module Triggers
316
+ def after_parentize
317
+ # should register on root element to be called when root is saved first time!?
318
+ end
319
+
320
+ def created!
321
+ self['created_at'] = Time.now
322
+ self['updated_at'] = Time.now
323
+ add_to_list_bottom unless in_list?
324
+ end
325
+
326
+ end
327
+
328
+ module Fields
329
+ def my_position
330
+ self[position_column]
331
+ end
332
+
333
+ def set_my_position new_position
334
+ if new_position != my_position
335
+ self[position_column] = new_position
336
+ save!
337
+ end
338
+ end
339
+
340
+ def [](field_name)
341
+ self.send field_name
342
+ end
343
+
344
+ def []=(key, value)
345
+ if set_allowed?(key)
346
+ @attributes[key.to_s] = value
347
+ elsif write_allowed?(key)
348
+ self.send("#{key}=", value)
349
+ end
350
+ save!
351
+ end
352
+
353
+ def ==(other)
354
+ return true if other.equal?(self)
355
+ return true if other.instance_of?(self.class) and other._id == self._id
356
+ false
357
+ end
358
+
359
+ def position_key
360
+ position_column.to_sym
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ class Array
367
+ def created!
368
+ each {|i| i.created! }
369
+ end
370
+ end
@@ -0,0 +1,18 @@
1
+ class Item
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ include ActsAsList::Mongoid
5
+
6
+ field :pos, :type => Integer
7
+ field :number, :type => Integer
8
+
9
+ acts_as_list :column => :pos
10
+
11
+ embedded_in :list, :inverse_of => :items
12
+ end
13
+
14
+ class List
15
+ include Mongoid::Document
16
+ field :name, :type => String
17
+ embeds_many :items
18
+ end
data/spec/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format nested --color
@@ -0,0 +1,104 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ require 'acts_as_list_mongoid'
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.created!
17
+ end
18
+
19
+ after :each do
20
+ Mongoid.database.collections.each do |coll|
21
+ coll.remove
22
+ end
23
+ end
24
+
25
+ def get_positions list
26
+ list.items.sort { |x,y| x.my_position <=> y.my_position }.map(&:number)
27
+ end
28
+
29
+ context "4 list items (1,2,3,4) that have parent_id pointing to first list container" do
30
+ describe '# initial configuration' do
31
+ it "should list items 1 to 4 in order" do
32
+ positions = get_positions @list
33
+ positions.should == [1, 2, 3, 4]
34
+ end
35
+ end
36
+
37
+ describe '#reordering' do
38
+ it "should move item 2 to position 3" do
39
+ @list.items[1].increment_position
40
+ @list.items[2].decrement_position
41
+ get_positions(@list).should == [1, 3, 2, 4]
42
+ end
43
+
44
+
45
+ it "should move item 2 to position 3" do
46
+ @list.items.where(:number => 2).first.move_lower
47
+ get_positions(@list).should == [1, 3, 2, 4]
48
+ end
49
+
50
+ it "should move item 2 to position 1" do
51
+ @list.items.where(:number => 2).first.move_higher
52
+ get_positions(@list).should == [2, 1, 3, 4]
53
+ end
54
+
55
+ it "should move item 1 to bottom" do
56
+ @list.items.where(:number => 1).first.move_to_bottom
57
+ get_positions(@list).should == [2, 3, 4, 1]
58
+ end
59
+
60
+ it "should move item 1 to top" do
61
+ @list.items.where(:number => 1).first.move_to_top
62
+ get_positions(@list).should == [1, 2, 3, 4]
63
+ end
64
+
65
+ it "should move item 2 to bottom" do
66
+ @list.items.where(:number => 2).first.move_to_bottom
67
+ get_positions(@list).should == [1, 3, 4, 2]
68
+ end
69
+
70
+ it "should move item 4 to top" do
71
+ @list.items.where(:number => 4).first.move_to_top
72
+ get_positions(@list).should == [4, 1, 2, 3]
73
+ end
74
+
75
+ it "should move item 3 to bottom" do
76
+ get_positions(@list).should == [1, 2, 3, 4]
77
+
78
+ @list.items.where(:number => 3).first.move_to_bottom
79
+ get_positions(@list).should == [1, 2, 4, 3]
80
+
81
+ end
82
+ end
83
+
84
+ describe 'relative position queries' do
85
+ it "should find item 2 to be lower item of item 1" do
86
+ expected = @list.items.where(:pos => 2).first
87
+ @list.items.where(:pos => 1).first.lower_item.should == expected
88
+ end
89
+
90
+ it "should not find any item higher than nr 1" do
91
+ @list.items.where(:pos => 1).first.higher_item.should == nil
92
+ end
93
+
94
+ it "should find item 3 to be higher item of item 4" do
95
+ expected = @list.items.where(:pos => 3).first
96
+ @list.items.where(:pos => 4).first.higher_item.should == expected
97
+ end
98
+
99
+ it "should not find item lower than item 4" do
100
+ @list.items.where(:pos => 4).first.lower_item.should == nil
101
+ end
102
+ end
103
+ end
104
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color --format nested
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require 'rspec/autorun'
3
+ require 'mongoid'
4
+ require 'mongoid_embedded_helper'
5
+
6
+ $:.unshift "#{File.dirname(__FILE__)}/../model/"
7
+
8
+ Mongoid.configure.master = Mongo::Connection.new.db('acts_as_list-test')
9
+
10
+
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_list_mongoid
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Kristian Mandrup
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-06 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongoid
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 0
31
+ - 0
32
+ - beta7
33
+ version: 2.0.0.beta7
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: mongoid_embedded_helper
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ segments:
45
+ - 0
46
+ - 2
47
+ - 3
48
+ version: 0.2.3
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ description: |-
52
+ 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.
53
+ The instances that take part in the list should have a +position+ field of type Integer.
54
+ email: kmandrup@gmail.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - README.markdown
61
+ files:
62
+ - .DS_Store
63
+ - README.markdown
64
+ - Rakefile
65
+ - VERSION
66
+ - example/example.rb
67
+ - init.rb
68
+ - lib/.DS_Store
69
+ - lib/acts_as_list_mongoid.rb
70
+ - lib/init.rb
71
+ - lib/mongoid/acts_as_list.rb
72
+ - model/embedded_item.rb
73
+ - spec/.rspec
74
+ - spec/acts_as_list/embedded/custom_embedded_spec.rb
75
+ - spec/spec.opts
76
+ - spec/spec_helper.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/rails/acts_as_list
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --charset=UTF-8
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ requirements: []
103
+
104
+ rubyforge_project:
105
+ rubygems_version: 1.3.7
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Gem version of acts_as_list for Mongoid
109
+ test_files:
110
+ - spec/acts_as_list/embedded/custom_embedded_spec.rb
111
+ - spec/spec_helper.rb