mongoid_orderable 5.2.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +73 -58
- data/LICENSE.txt +20 -20
- data/README.md +256 -150
- data/Rakefile +24 -21
- data/lib/config/locales/en.yml +12 -9
- data/lib/mongoid/orderable.rb +29 -20
- data/lib/mongoid/orderable/configs/field_config.rb +79 -0
- data/lib/mongoid/orderable/configs/global_config.rb +26 -0
- data/lib/mongoid/orderable/engine.rb +204 -0
- data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -15
- data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
- data/lib/mongoid/orderable/generators/base.rb +21 -0
- data/lib/mongoid/orderable/generators/helpers.rb +29 -0
- data/lib/mongoid/orderable/generators/listable.rb +41 -0
- data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
- data/lib/mongoid/orderable/generators/movable.rb +62 -0
- data/lib/mongoid/orderable/generators/position.rb +26 -0
- data/lib/mongoid/orderable/generators/scope.rb +26 -0
- data/lib/mongoid/orderable/installer.rb +63 -0
- data/lib/mongoid/orderable/mixins/callbacks.rb +29 -0
- data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
- data/lib/mongoid/orderable/mixins/listable.rb +49 -0
- data/lib/mongoid/orderable/mixins/movable.rb +60 -0
- data/lib/mongoid/orderable/version.rb +7 -0
- data/lib/mongoid_orderable.rb +29 -56
- data/spec/mongoid/orderable_spec.rb +1486 -1380
- data/spec/spec_helper.rb +21 -21
- metadata +44 -41
- data/.gitignore +0 -4
- data/.rspec +0 -2
- data/.rubocop.yml +0 -6
- data/.rubocop_todo.yml +0 -88
- data/.rvmrc +0 -1
- data/.travis.yml +0 -48
- data/CONTRIBUTING.md +0 -118
- data/Dangerfile +0 -1
- data/Gemfile +0 -26
- data/RELEASING.md +0 -68
- data/lib/mongoid/orderable/callbacks.rb +0 -79
- data/lib/mongoid/orderable/configuration.rb +0 -58
- data/lib/mongoid/orderable/errors.rb +0 -2
- data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -6
- data/lib/mongoid/orderable/generator.rb +0 -33
- data/lib/mongoid/orderable/generator/helpers.rb +0 -27
- data/lib/mongoid/orderable/generator/listable.rb +0 -39
- data/lib/mongoid/orderable/generator/movable.rb +0 -60
- data/lib/mongoid/orderable/generator/position.rb +0 -24
- data/lib/mongoid/orderable/generator/scope.rb +0 -17
- data/lib/mongoid/orderable/helpers.rb +0 -49
- data/lib/mongoid/orderable/listable.rb +0 -47
- data/lib/mongoid/orderable/movable.rb +0 -56
- data/lib/mongoid/orderable/orderable_class.rb +0 -47
- data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
- data/lib/mongoid_orderable/version.rb +0 -3
- data/mongoid_orderable.gemspec +0 -26
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Errors
|
6
|
+
class TransactionFailed < ::Mongoid::Errors::MongoidError
|
7
|
+
def initialize(error)
|
8
|
+
super _compose_message(error)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def _compose_message(error)
|
14
|
+
compose_message 'transaction_failed'
|
15
|
+
@summary = error.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Base
|
7
|
+
attr_reader :klass
|
8
|
+
|
9
|
+
def initialize(klass)
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def generate_method(name, &block)
|
16
|
+
klass.send(:define_method, name, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Helpers < Base
|
7
|
+
def generate
|
8
|
+
self_class = klass
|
9
|
+
|
10
|
+
klass.class_eval <<~KLASS, __FILE__, __LINE__ + 1
|
11
|
+
def orderable_top(field = nil)
|
12
|
+
field ||= default_orderable_field
|
13
|
+
#{self_class}.orderable_configs[field][:base]
|
14
|
+
end
|
15
|
+
|
16
|
+
def orderable_field(field = nil)
|
17
|
+
field ||= default_orderable_field
|
18
|
+
#{self_class}.orderable_configs[field][:field]
|
19
|
+
end
|
20
|
+
KLASS
|
21
|
+
|
22
|
+
generate_method(:orderable_inherited_class) do
|
23
|
+
self_class.orderable_configs.any? {|_field, conf| conf[:inherited] } ? self_class : self.class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Listable < Base
|
7
|
+
def generate(field_name)
|
8
|
+
generate_list_helpers(field_name)
|
9
|
+
generate_aliased_helpers(field_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def generate_list_helpers(field_name)
|
15
|
+
generate_method("next_#{field_name}_item") do
|
16
|
+
next_item(field_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
generate_method("next_#{field_name}_items") do
|
20
|
+
next_items(field_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
generate_method("previous_#{field_name}_item") do
|
24
|
+
previous_item(field_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
generate_method("previous_#{field_name}_items") do
|
28
|
+
previous_items(field_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_aliased_helpers(field_name)
|
33
|
+
klass.class_eval do
|
34
|
+
alias_method "prev_#{field_name}_items", "previous_#{field_name}_items"
|
35
|
+
alias_method "prev_#{field_name}_item", "previous_#{field_name}_item"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class LockCollection
|
7
|
+
def generate(collection_name)
|
8
|
+
return unless collection_name
|
9
|
+
model_name = collection_name.to_s.singularize.classify
|
10
|
+
return if model_exists?(model_name)
|
11
|
+
::Mongoid::Orderable.class_eval <<~KLASS, __FILE__, __LINE__ + 1
|
12
|
+
module Models
|
13
|
+
class #{model_name}
|
14
|
+
include Mongoid::Document
|
15
|
+
|
16
|
+
store_in collection: :#{collection_name}
|
17
|
+
|
18
|
+
field :scope, type: String
|
19
|
+
|
20
|
+
index({ scope: 1 }, { unique: 1 })
|
21
|
+
end
|
22
|
+
end
|
23
|
+
KLASS
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def model_exists?(model_name)
|
29
|
+
base = ::Mongoid::Orderable::Models
|
30
|
+
!!(defined?(base) && base.const_get(model_name))
|
31
|
+
rescue NameError
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Movable < Base
|
7
|
+
def generate(field_name)
|
8
|
+
generate_move_to_helpers(field_name)
|
9
|
+
generate_insert_at_helpers(field_name)
|
10
|
+
generate_shorthand_helpers(field_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def generate_move_to_helpers(field_name)
|
16
|
+
generate_method("move_#{field_name}_to") do |target_position|
|
17
|
+
move_field_to target_position, field: field_name
|
18
|
+
end
|
19
|
+
|
20
|
+
generate_method("move_#{field_name}_to!") do |target_position|
|
21
|
+
move_field_to target_position, field: field_name
|
22
|
+
save
|
23
|
+
end
|
24
|
+
|
25
|
+
generate_method("move_#{field_name}_to=") do |target_position|
|
26
|
+
move_field_to target_position, field: field_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_insert_at_helpers(field_name)
|
31
|
+
klass.class_eval do
|
32
|
+
alias_method "insert_#{field_name}_at!", "move_#{field_name}_to!"
|
33
|
+
alias_method "insert_#{field_name}_at", "move_#{field_name}_to"
|
34
|
+
alias_method "insert_#{field_name}_at=", "move_#{field_name}_to="
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_shorthand_helpers(field_name)
|
39
|
+
%i[top bottom].each do |symbol|
|
40
|
+
generate_method "move_#{field_name}_to_#{symbol}" do
|
41
|
+
move_to symbol, field: field_name
|
42
|
+
end
|
43
|
+
|
44
|
+
generate_method "move_#{field_name}_to_#{symbol}!" do
|
45
|
+
move_to! symbol, field: field_name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
%i[higher lower].each do |symbol|
|
50
|
+
generate_method "move_#{field_name}_#{symbol}" do
|
51
|
+
move_to symbol, field: field_name
|
52
|
+
end
|
53
|
+
|
54
|
+
generate_method "move_#{field_name}_#{symbol}!" do
|
55
|
+
move_to! symbol, field: field_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Position < Base
|
7
|
+
def generate(field_name)
|
8
|
+
klass.class_eval <<~KLASS, __FILE__, __LINE__ + 1
|
9
|
+
def orderable_position(field = nil)
|
10
|
+
field ||= default_orderable_field
|
11
|
+
send "orderable_\#{field}_position"
|
12
|
+
end
|
13
|
+
KLASS
|
14
|
+
|
15
|
+
generate_method("orderable_#{field_name}_position") do
|
16
|
+
send field_name
|
17
|
+
end
|
18
|
+
|
19
|
+
generate_method("orderable_#{field_name}_position=") do |value|
|
20
|
+
send "#{field_name}=", value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Generators
|
6
|
+
class Scope < Base
|
7
|
+
def generate(field_name, order_scope)
|
8
|
+
criteria = criteria(order_scope)
|
9
|
+
klass.class_eval do
|
10
|
+
scope "orderable_#{field_name}_scope", criteria
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def criteria(order_scope)
|
17
|
+
case order_scope
|
18
|
+
when Proc then order_scope
|
19
|
+
when Array then ->(doc) { where(order_scope.each_with_object({}) {|f, h| h[f] = doc.send(f) }) }
|
20
|
+
else ->(_doc) { where({}) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
class Installer
|
6
|
+
attr_reader :klass, :config
|
7
|
+
|
8
|
+
def initialize(klass, options = {})
|
9
|
+
@klass = klass
|
10
|
+
@config = Mongoid::Orderable::Configs::FieldConfig.new(klass, options).options
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
add_db_field
|
15
|
+
add_db_index if config[:index]
|
16
|
+
save_config
|
17
|
+
include_mixins
|
18
|
+
generate_all_helpers
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def field_name
|
24
|
+
config[:field_options][:as] || config[:field]
|
25
|
+
end
|
26
|
+
|
27
|
+
def order_scope
|
28
|
+
config[:scope]
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_db_field
|
32
|
+
klass.field(config[:field], { type: Integer }.reverse_merge(config[:field_options]))
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_db_index
|
36
|
+
spec = [[config[:field], 1]]
|
37
|
+
config[:scope].each {|field| spec.unshift([field, 1]) } if config[:scope].is_a?(Array)
|
38
|
+
klass.index(Hash[spec])
|
39
|
+
end
|
40
|
+
|
41
|
+
def save_config
|
42
|
+
klass.orderable_configs ||= {}
|
43
|
+
klass.orderable_configs = klass.orderable_configs.merge(field_name => config)
|
44
|
+
end
|
45
|
+
|
46
|
+
def include_mixins
|
47
|
+
klass.send :include, Mixins::Helpers
|
48
|
+
klass.send :include, Mixins::Callbacks
|
49
|
+
klass.send :include, Mixins::Movable
|
50
|
+
klass.send :include, Mixins::Listable
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_all_helpers
|
54
|
+
Generators::Scope.new(klass).generate(field_name, order_scope)
|
55
|
+
Generators::Position.new(klass).generate(field_name)
|
56
|
+
Generators::Movable.new(klass).generate(field_name)
|
57
|
+
Generators::Listable.new(klass).generate(field_name)
|
58
|
+
Generators::Helpers.new(klass).generate
|
59
|
+
Generators::LockCollection.new.generate(config[:lock_collection])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Callbacks
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
ORDERABLE_TRANSACTION_KEY = :__mongoid_orderable_in_txn
|
10
|
+
|
11
|
+
included do
|
12
|
+
around_save :orderable_update_positions
|
13
|
+
after_destroy :orderable_remove_positions, unless: -> { embedded? && _root.destroyed? }
|
14
|
+
|
15
|
+
delegate :update_positions,
|
16
|
+
:remove_positions,
|
17
|
+
to: :orderable_engine,
|
18
|
+
prefix: :orderable
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def orderable_engine
|
23
|
+
@orderable_engine ||= Mongoid::Orderable::Engine.new(self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Helpers
|
7
|
+
def orderable_keys
|
8
|
+
Array(orderable_inherited_class.orderable_configs.try(:keys))
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_orderable_field
|
12
|
+
self.class.orderable_configs.detect {|_c, conf| conf[:default] }.try(:first) || orderable_keys.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def orderable_scope(field = nil)
|
16
|
+
field ||= default_orderable_field
|
17
|
+
|
18
|
+
if embedded?
|
19
|
+
_parent.send(_association.name).send("orderable_#{field}_scope", self)
|
20
|
+
else
|
21
|
+
orderable_inherited_class.send("orderable_#{field}_scope", self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def orderable_scope_changed?(field)
|
26
|
+
!orderable_scope(field).where(_id: _id).exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def orderable_bottom(field = nil, in_list = true)
|
30
|
+
field ||= default_orderable_field
|
31
|
+
f = orderable_field(field)
|
32
|
+
max = orderable_scope(field).ne(f => nil).max(f)
|
33
|
+
return orderable_top(field) unless max
|
34
|
+
in_list ? max : max.next
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Listable
|
7
|
+
def in_list?(field = nil)
|
8
|
+
persisted? && !orderable_position(field).nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns items above the current document.
|
12
|
+
# Items with a position lower than this document's position.
|
13
|
+
def previous_items(field = nil)
|
14
|
+
field ||= default_orderable_field
|
15
|
+
orderable_scope(field).lt(orderable_field(field) => send(field))
|
16
|
+
end
|
17
|
+
alias prev_items previous_items
|
18
|
+
|
19
|
+
# Returns items below the current document.
|
20
|
+
# Items with a position greater than this document's position.
|
21
|
+
def next_items(field = nil)
|
22
|
+
field ||= default_orderable_field
|
23
|
+
orderable_scope(field).gt(orderable_field(field) => send(field))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the previous item in the list
|
27
|
+
def previous_item(field = nil)
|
28
|
+
field ||= default_orderable_field
|
29
|
+
orderable_scope(field).where(orderable_field(field) => send(field) - 1).first
|
30
|
+
end
|
31
|
+
alias prev_item previous_item
|
32
|
+
|
33
|
+
# Returns the next item in the list
|
34
|
+
def next_item(field = nil)
|
35
|
+
field ||= default_orderable_field
|
36
|
+
orderable_scope(field).where(orderable_field(field) => send(field) + 1).first
|
37
|
+
end
|
38
|
+
|
39
|
+
def first?(field = nil)
|
40
|
+
in_list?(field) && orderable_position(field) == orderable_top(field)
|
41
|
+
end
|
42
|
+
|
43
|
+
def last?(field = nil)
|
44
|
+
in_list?(field) && orderable_position(field) == orderable_bottom(field)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|