acts_as_list_mongoid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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