mongoid_orderable 4.1.0 → 4.1.1

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.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YThjMDBhYzQ5YmQ4NzJlZWRjMjdhZTIyNTdmMTkwMmE0YmZlZWI5NQ==
5
- data.tar.gz: !binary |-
6
- ZjBmZDk1OTM1MmE1ZGNiMmFiMDU1Y2IxYTAwMzZlZTkzZWFiMjhlYQ==
2
+ SHA1:
3
+ metadata.gz: cea70aa8c9e3b86f49203e6f1bb17b45a994e45a
4
+ data.tar.gz: 99ad3433535e36e7e4f9f579f1ab4c7640f5552c
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NDc5NWY5NTlkMDYzNWMzZGI3OGRiNjc0MDEzNjdhYWJiMDA5MTkzOTE5OGMx
10
- YzA4NWZmNmZmZTJmNTZmNTdlNDFjZDk2NmJkOGQyNDMyMTM2Mjg2ZjU5NGE1
11
- MmI0YzU2MmVhODdmMWY1MGI5ZDhhZTVkNGQ0NjRlNzNlMzI4ZDU=
12
- data.tar.gz: !binary |-
13
- NTYyN2E1ZTVlNDQ5YzZhNDY5Y2UyNDMzZWVhZWQ4YWZmMTJkNTI0ZDNhNzIy
14
- ZDg0MTkyZjY3YWVkNzdmNWM5ZDY3ZWJiNDMxYzk2NDM0ZWUxOTFiMmMxMWEy
15
- ZjE2MmRiZDcyY2U5NTg1MjU3ODkwOGIwMDIxMDY0NDMyMmExMzQ=
6
+ metadata.gz: 52b38d32f9272161134dd2c4b04d70549e6b8fe7034b0838c12d243e420702889ae36f2b43463c22b0afafc5447817414f0d402d9441b9a6847434c328f505c4
7
+ data.tar.gz: 1608265030534f0bb65c9911014cb1e3d6a54691a037644cdc31295aec872c58b4edfc84414cf847e2c35497b2629d87606f6fe48b993c3b2ca34a237d97890b
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use 1.9.3@mongoid_orderable --create
1
+ rvm use 2.0.0@mongoid_orderable --create
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # master
2
2
 
3
+ # 4.1.1
4
+
5
+ * Fix: index should respect scope (@joeyAghion)
6
+
3
7
  # 4.1.0
4
8
 
5
9
  * Resolving scope foreign key form passed relation name (@dsci)
data/README.md CHANGED
@@ -10,6 +10,7 @@ Mongoid::Orderable is a ordered list implementation for your mongoid models.
10
10
  * It supports mutators api
11
11
  * It correctly assigns the position while moving document between scopes
12
12
  * It supports mongoid 2, 3 and 4
13
+ * It supports specifying multiple orderable columns
13
14
 
14
15
  # How?
15
16
 
@@ -18,6 +19,7 @@ gem 'mongoid_orderable'
18
19
  ```
19
20
 
20
21
  Gem has the same api as others. Just include Mongoid::Orderable into your model and call `orderable` method.
22
+ Embedded objects are automatically scoped by their parent.
21
23
 
22
24
  ```
23
25
  class Item
@@ -64,6 +66,58 @@ item.next_item # returns the next item in the list
64
66
  item.previous_item # returns the previous item in the list
65
67
  ```
66
68
 
69
+ # Multiple Columns
70
+
71
+ You can also define multiple orderable columns for any class including the Mongoid::Orderable module.
72
+
73
+ ```
74
+ class Book
75
+ include Mongoid::Document
76
+ include Mongoid::Orderable
77
+
78
+ orderable base: 0
79
+ orderable column: sno, as: :serial_no
80
+ end
81
+ ```
82
+
83
+ The above defines two different orderable_columns on Book - *position* and *serial_no*.
84
+ The following helpers are generated in this case:
85
+
86
+ ```
87
+ item.move_#{column_name}_to
88
+ item.move_#{column_name}_to=
89
+ item.move_#{column_name}_to!
90
+
91
+ item.move_#{column_name}_to_top
92
+ item.move_#{column_name}_to_bottom
93
+ item.move_#{column_name}_higher
94
+ item.move_#{column_name}_lower
95
+
96
+ item.next_#{column_name}_items
97
+ item.previous_#{column_name}_items
98
+
99
+ item.next_#{column_name}_item
100
+ item.previous_#{column_name}_item
101
+ ```
102
+
103
+ *where column_name is either **position** or **serial_no**.*
104
+
105
+ When a model defines multiple orderable columns, the original helpers are also available and work on the first orderable column.
106
+
107
+ ```
108
+ @book1 = Book.create!
109
+ @book2 = Book.create!
110
+ @book2 => #<Book _id: 53a16a2ba1bde4f746000001, serial_no: 1, position: 1>
111
+ @book2.move_to! :top # this will change the :position of the book to 0 (not serial_no)
112
+ @book2 => #<Book _id: 53a16a2ba1bde4f746000001, serial_no: 1, position: 0>
113
+ ```
114
+
115
+ To specify any other orderable column as default pass the **default: true** option with orderable.
116
+
117
+ ```
118
+ orderable column: sno, as: :serial_no, default: true
119
+ ```
120
+
67
121
  # Contributing
68
122
 
69
123
  Fork && Patch && Spec && Push && Pull request.
@@ -0,0 +1,9 @@
1
+ en:
2
+ mongoid:
3
+ errors:
4
+ messages:
5
+ invalid_target_position:
6
+ message: "`%{value}` is not an acceptable value for target position."
7
+ summary: "Mongoid::Orderable accepts: a Numeric, a numeric string,
8
+ :top, :bottom, or nil."
9
+ resolution: "You must give an acceptable value for target position."
@@ -1,239 +1,22 @@
1
1
  module Mongoid::Orderable
2
2
  extend ActiveSupport::Concern
3
3
 
4
- module ClassMethods
5
- def orderable options = {}
6
- configuration = {
7
- :column => :position,
8
- :index => true,
9
- :scope => nil,
10
- :base => 1
11
- }
12
-
13
- configuration.merge! options if options.is_a?(Hash)
14
-
15
- if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
16
- scope_relation = self.relations[configuration[:scope].to_s]
17
- if scope_relation
18
- configuration[:scope] = scope_relation.key.to_sym
19
- else
20
- configuration[:scope] = "#{configuration[:scope]}_id".to_sym
21
- end
22
- elsif configuration[:scope].is_a?(String)
23
- configuration[:scope] = configuration[:scope].to_sym
24
- end
25
-
26
- field configuration[:column], orderable_field_opts(configuration)
27
- if configuration[:index]
28
- if MongoidOrderable.mongoid2?
29
- index configuration[:column]
30
- else
31
- index(configuration[:column] => 1)
32
- end
33
- end
34
-
35
- case configuration[:scope]
36
- when Symbol then
37
- scope :orderable_scope, lambda { |document|
38
- where(configuration[:scope] => document.send(configuration[:scope])) }
39
- when Proc then
40
- scope :orderable_scope, configuration[:scope]
41
- else
42
- scope :orderable_scope, lambda { |document| where({}) }
43
- end
44
-
45
- define_method :orderable_column do
46
- configuration[:column]
47
- end
48
-
49
- define_method :orderable_base do
50
- configuration[:base]
51
- end
52
-
53
- self_class = self
54
- define_method :orderable_inherited_class do
55
- self_class if configuration[:inherited]
56
- end
57
-
58
- before_save :add_to_list
59
- after_destroy :remove_from_list
60
- end
61
-
62
- private
63
-
64
- def orderable_field_opts(configuration)
65
- field_opts = { :type => Integer }
66
- field_opts.merge!(configuration.slice(:as))
67
- field_opts
68
- end
69
- end
70
-
71
- ##
72
- # Returns items above the current document.
73
- # Items with a position lower than this document's position.
74
- def previous_items
75
- orderable_scoped.where(orderable_column.lt => self.position)
76
- end
77
- alias_method :prev_items, :previous_items
78
-
79
- ##
80
- # Returns items below the current document.
81
- # Items with a position greater than this document's position.
82
- def next_items
83
- orderable_scoped.where(orderable_column.gt => self.position)
84
- end
85
-
86
- # returns the previous item in the list
87
- def previous_item
88
- if previous_items.present?
89
- previous_position = self.position - 1
90
- orderable_scoped.where(:position => previous_position).first
91
- else
92
- nil
93
- end
94
- end
95
- alias_method :prev_item, :previous_item
96
-
97
- # returns the next item in the list
98
- def next_item
99
- if next_items.present?
100
- next_position = self.position + 1
101
- orderable_scoped.where(:position => next_position).first
102
- else
103
- nil
104
- end
105
- end
106
-
107
- def move_to! target_position
108
- @move_to = target_position
109
- save
110
- end
111
- alias_method :insert_at!, :move_to!
112
-
113
- def move_to target_position
114
- @move_to = target_position
115
- end
116
- alias_method :insert_at, :move_to
117
-
118
- def move_to= target_position
119
- @move_to = target_position
120
- end
121
- alias_method :insert_at=, :move_to=
122
-
123
- [:top, :bottom].each do |symbol|
124
- define_method "move_to_#{symbol}" do
125
- move_to symbol
126
- end
127
-
128
- define_method "move_to_#{symbol}!" do
129
- move_to! symbol
130
- end
131
- end
132
-
133
- [:higher, :lower].each do |symbol|
134
- define_method "move_#{symbol}" do
135
- move_to symbol
136
- end
137
-
138
- define_method "move_#{symbol}!" do
139
- move_to! symbol
140
- end
141
- end
142
-
143
- def first?
144
- in_list? && orderable_position == orderable_base
145
- end
146
-
147
- def last?
148
- in_list? && orderable_position == bottom_orderable_position
149
- end
150
-
151
- def in_list?
152
- !orderable_position.nil?
153
- end
4
+ included do
5
+ include Mongoid::Orderable::Helpers
6
+ include Mongoid::Orderable::Callbacks
7
+ include Mongoid::Orderable::Movable
8
+ include Mongoid::Orderable::Listable
154
9
 
155
- def add_to_list
156
- apply_position @move_to
10
+ class_attribute :orderable_configurations
157
11
  end
158
12
 
159
- def remove_from_list
160
- MongoidOrderable.inc orderable_scoped.where(orderable_column.gt => orderable_position), orderable_column, -1
161
- end
162
-
163
- private
164
-
165
- def orderable_position
166
- send orderable_column
167
- end
168
-
169
- def orderable_position= value
170
- send "#{orderable_column}=", value
171
- end
172
-
173
- def orderable_scoped
174
- if embedded?
175
- send(MongoidOrderable.metadata(self).inverse).send(MongoidOrderable.metadata(self).name).orderable_scope(self)
176
- else
177
- (orderable_inherited_class || self.class).orderable_scope(self)
178
- end
179
- end
180
-
181
- def orderable_scope_changed?
182
- if Mongoid.respond_to?(:unit_of_work)
183
- Mongoid.unit_of_work do
184
- orderable_scope_changed_query
185
- end
186
- else
187
- orderable_scope_changed_query
188
- end
189
- end
190
-
191
- def orderable_scope_changed_query
192
- !orderable_scoped.where(:_id => _id).exists?
193
- end
194
-
195
- def apply_position target_position
196
- if persisted? && !embedded? && orderable_scope_changed?
197
- self.class.unscoped.find(_id).remove_from_list
198
- self.orderable_position = nil
199
- end
200
-
201
- return if !target_position && in_list?
13
+ module ClassMethods
202
14
 
203
- target_position = target_position_to_position target_position
15
+ def orderable options = {}
16
+ configuration = Mongoid::Orderable::Configuration.build(self, options)
204
17
 
205
- unless in_list?
206
- MongoidOrderable.inc orderable_scoped.where(orderable_column.gte => target_position), orderable_column, 1
207
- else
208
- MongoidOrderable.inc(orderable_scoped.where(orderable_column.gte => target_position, orderable_column.lt => orderable_position), orderable_column, 1) if target_position < orderable_position
209
- MongoidOrderable.inc(orderable_scoped.where(orderable_column.gt => orderable_position, orderable_column.lte => target_position), orderable_column, -1) if target_position > orderable_position
18
+ Mongoid::Orderable::OrderableClass.setup(self, configuration)
210
19
  end
211
20
 
212
- self.orderable_position = target_position
213
- end
214
-
215
- def target_position_to_position target_position
216
- target_position = :bottom unless target_position
217
-
218
- target_position = case target_position.to_sym
219
- when :top then orderable_base
220
- when :bottom then bottom_orderable_position
221
- when :higher then orderable_position.pred
222
- when :lower then orderable_position.next
223
- end unless target_position.is_a? Numeric
224
-
225
- target_position = orderable_base if target_position < orderable_base
226
- target_position = bottom_orderable_position if target_position > bottom_orderable_position
227
- target_position
228
21
  end
229
-
230
- def bottom_orderable_position
231
- @bottom_orderable_position = begin
232
- positions_list = orderable_scoped.distinct(orderable_column)
233
- return orderable_base if positions_list.empty?
234
- max = positions_list.map(&:to_i).max.to_i
235
- in_list? ? max : max.next
236
- end
237
- end
238
-
239
22
  end
@@ -0,0 +1,75 @@
1
+ module Mongoid
2
+ module Orderable
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+
8
+ protected
9
+
10
+ def add_to_list
11
+ self.orderable_keys.each do |column|
12
+ apply_position column, move_all[column]
13
+ end
14
+ end
15
+
16
+ def remove_from_list
17
+ self.orderable_keys.each do |column|
18
+ remove_position_from_list column
19
+ end
20
+ end
21
+
22
+ def remove_position_from_list(column)
23
+ col, pos = orderable_column(column), orderable_position(column)
24
+ MongoidOrderable.inc orderable_scoped(column).where(col.gt => pos), col, -1
25
+ end
26
+
27
+ def apply_position column, target_position
28
+ if persisted? && !embedded? && orderable_scope_changed?(column)
29
+ self.class.unscoped.find(_id).remove_position_from_list(column)
30
+ self.send("orderable_#{column}_position=", nil)
31
+ end
32
+
33
+ return if !target_position && in_list?(column)
34
+
35
+ target_position = target_position_to_position column, target_position
36
+ scope, col, pos = orderable_scoped(column), orderable_column(column), orderable_position(column)
37
+
38
+ unless in_list?(column)
39
+ MongoidOrderable.inc scope.where(col.gte => target_position), col, 1
40
+ else
41
+ MongoidOrderable.inc(scope.where(col.gte => target_position, col.lt => pos), col, 1) if target_position < pos
42
+ MongoidOrderable.inc(scope.where(col.gt => pos, col.lte => target_position), col, -1) if target_position > pos
43
+ end
44
+
45
+ self.send("orderable_#{column}_position=", target_position)
46
+ end
47
+
48
+ def target_position_to_position column, target_position
49
+ target_position = :bottom unless target_position
50
+
51
+ target_position = case target_position.to_s
52
+ when 'top' then orderable_base(column)
53
+ when 'bottom' then bottom_orderable_position(column)
54
+ when 'higher' then orderable_position(column).pred
55
+ when 'lower' then orderable_position(column).next
56
+ when /\A\d+\Z/ then target_position.to_i
57
+ else raise Mongoid::Orderable::Errors::InvalidTargetPosition.new target_position
58
+ end unless target_position.is_a? Numeric
59
+
60
+ target_position = orderable_base(column) if target_position < orderable_base(column)
61
+ target_position = bottom_orderable_position(column) if target_position > bottom_orderable_position(column)
62
+ target_position
63
+ end
64
+
65
+ end
66
+
67
+ module ClassMethods
68
+ def add_orderable_callbacks
69
+ before_save :add_to_list
70
+ after_destroy :remove_from_list
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,60 @@
1
+ module Mongoid
2
+ module Orderable
3
+ class Configuration
4
+
5
+ CONFIG_OPTIONS = %w(column scope foreign_key inherited base index default).map(&:to_sym)
6
+ FIELD_OPTIONS = %w(as).map(&:to_sym)
7
+ VALID_OPTIONS = CONFIG_OPTIONS + FIELD_OPTIONS
8
+
9
+ attr_reader :orderable_class, :options
10
+
11
+ def initialize(parent, options = {})
12
+ @orderable_class = parent
13
+ set_options(options)
14
+ set_field_options
15
+ set_orderable_scope
16
+ end
17
+
18
+ def default_configuration
19
+ { :column => :position,
20
+ :index => true,
21
+ :scope => nil,
22
+ :base => 1,
23
+ :field_opts => { :type => Integer }}
24
+ end
25
+
26
+ def self.build(parent, options = {})
27
+ new(parent, options).options
28
+ end
29
+
30
+ protected
31
+
32
+ def set_options(options)
33
+ @options = default_configuration
34
+ return unless options.is_a? Hash
35
+ @options.merge! options.symbolize_keys.slice(*VALID_OPTIONS)
36
+ end
37
+
38
+ def set_field_options
39
+ FIELD_OPTIONS.each do |key|
40
+ next unless options.has_key? key
41
+ @options[:field_opts][key] = options.delete(key)
42
+ end
43
+ end
44
+
45
+ def set_orderable_scope
46
+ if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
47
+ scope_relation = @orderable_class.relations[options[:scope].to_s]
48
+ if scope_relation
49
+ @options[:scope] = scope_relation.key.to_sym
50
+ else
51
+ @options[:scope] = "#{options[:scope]}_id".to_sym
52
+ end
53
+ elsif options[:scope].is_a?(String)
54
+ @options[:scope] = options[:scope].to_sym
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end