mongoid_orderable 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
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