mongoid_orderable 5.2.0 → 6.0.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.
- 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
|