mongoid_orderable 5.0.0 → 6.0.2
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 +83 -22
- data/LICENSE.txt +20 -0
- data/README.md +256 -149
- data/Rakefile +24 -6
- data/lib/config/locales/en.yml +12 -9
- data/lib/mongoid/orderable.rb +29 -22
- data/lib/mongoid/orderable/configs/field_config.rb +79 -0
- data/lib/mongoid/orderable/configs/global_config.rb +26 -0
- data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -18
- 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/handlers/base.rb +167 -0
- data/lib/mongoid/orderable/handlers/document.rb +24 -0
- data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -0
- data/lib/mongoid/orderable/handlers/document_transactional.rb +35 -0
- data/lib/mongoid/orderable/handlers/transaction.rb +71 -0
- data/lib/mongoid/orderable/installer.rb +63 -0
- data/lib/mongoid/orderable/mixins/callbacks.rb +43 -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 +33 -54
- data/spec/integration/concurrency_spec.rb +232 -0
- data/spec/integration/customized_spec.rb +31 -0
- data/spec/integration/embedded_spec.rb +41 -0
- data/spec/integration/foreign_key_spec.rb +33 -0
- data/spec/integration/inherited_spec.rb +54 -0
- data/spec/integration/multiple_fields_spec.rb +554 -0
- data/spec/integration/multiple_scoped_spec.rb +63 -0
- data/spec/integration/no_indexed_spec.rb +23 -0
- data/spec/integration/scoped_spec.rb +151 -0
- data/spec/integration/simple_spec.rb +184 -0
- data/spec/integration/string_scoped_spec.rb +28 -0
- data/spec/integration/zero_based_spec.rb +161 -0
- data/spec/spec_helper.rb +42 -30
- data/spec/support/models.rb +122 -0
- metadata +75 -41
- data/.gitignore +0 -4
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/.travis.yml +0 -14
- data/Gemfile +0 -18
- data/lib/mongoid/orderable/callbacks.rb +0 -75
- data/lib/mongoid/orderable/configuration.rb +0 -60
- data/lib/mongoid/orderable/errors.rb +0 -2
- data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -14
- data/lib/mongoid/orderable/generator.rb +0 -34
- data/lib/mongoid/orderable/generator/helpers.rb +0 -29
- data/lib/mongoid/orderable/generator/listable.rb +0 -41
- data/lib/mongoid/orderable/generator/movable.rb +0 -62
- data/lib/mongoid/orderable/generator/position.rb +0 -26
- data/lib/mongoid/orderable/generator/scope.rb +0 -17
- data/lib/mongoid/orderable/helpers.rb +0 -50
- data/lib/mongoid/orderable/listable.rb +0 -49
- data/lib/mongoid/orderable/movable.rb +0 -58
- data/lib/mongoid/orderable/orderable_class.rb +0 -51
- data/lib/mongoid_orderable/mongoid/contexts/enumerable.rb +0 -15
- data/lib/mongoid_orderable/mongoid/contexts/mongo.rb +0 -18
- data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
- data/lib/mongoid_orderable/mongoid/criteria.rb +0 -4
- data/lib/mongoid_orderable/version.rb +0 -3
- data/mongoid_orderable.gemspec +0 -26
- data/spec/mongoid/orderable_spec.rb +0 -1413
data/Rakefile
CHANGED
@@ -1,6 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
warn e.message
|
9
|
+
warn 'Run `bundle install` to install missing gems'
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
|
13
|
+
Bundler::GemHelper.install_tasks
|
14
|
+
|
15
|
+
require 'rspec/core'
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
18
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rubocop/rake_task'
|
22
|
+
RuboCop::RakeTask.new(:rubocop)
|
23
|
+
|
24
|
+
task default: %i[rubocop spec]
|
data/lib/config/locales/en.yml
CHANGED
@@ -1,9 +1,12 @@
|
|
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
|
-
|
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, :top, :bottom, or nil."
|
8
|
+
resolution: "You must give an acceptable value for target position."
|
9
|
+
transaction_failed:
|
10
|
+
message: "Transaction failed due to a database-level conflict."
|
11
|
+
summary: ""
|
12
|
+
resolution: "Please retry the transaction."
|
data/lib/mongoid/orderable.rb
CHANGED
@@ -1,22 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
MUTEX = Mutex.new
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def configure
|
9
|
+
yield(config) if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def config
|
13
|
+
@config || MUTEX.synchronize { @config = ::Mongoid::Orderable::Configs::GlobalConfig.new }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
class_attribute :orderable_configs
|
21
|
+
end
|
22
|
+
|
23
|
+
class_methods do
|
24
|
+
def orderable(options = {})
|
25
|
+
Mongoid::Orderable::Installer.new(self, options).setup
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Configs
|
6
|
+
class FieldConfig
|
7
|
+
CONFIG_OPTIONS = %i[field
|
8
|
+
scope
|
9
|
+
foreign_key
|
10
|
+
inherited
|
11
|
+
base
|
12
|
+
index
|
13
|
+
default
|
14
|
+
use_transactions
|
15
|
+
transaction_max_retries
|
16
|
+
lock_collection].freeze
|
17
|
+
ALIASES = { column: :field }.freeze
|
18
|
+
FIELD_OPTIONS = %i[as].freeze
|
19
|
+
VALID_OPTIONS = (CONFIG_OPTIONS | FIELD_OPTIONS).freeze
|
20
|
+
|
21
|
+
attr_reader :orderable_class,
|
22
|
+
:options
|
23
|
+
|
24
|
+
def initialize(parent, options = {})
|
25
|
+
@orderable_class = parent
|
26
|
+
assign_options(options)
|
27
|
+
set_field_options
|
28
|
+
set_orderable_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def global_config
|
32
|
+
cfg = Mongoid::Orderable.config
|
33
|
+
{ field: cfg.field,
|
34
|
+
as: cfg.as,
|
35
|
+
index: cfg.index,
|
36
|
+
base: cfg.base,
|
37
|
+
use_transactions: cfg.use_transactions,
|
38
|
+
transaction_max_retries: cfg.transaction_max_retries,
|
39
|
+
lock_collection: cfg.lock_collection }
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def assign_options(options)
|
45
|
+
@options = global_config
|
46
|
+
return unless options.is_a?(Hash)
|
47
|
+
@options = @options.merge(options.symbolize_keys.transform_keys {|k| ALIASES[k] || k }).slice(*VALID_OPTIONS)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_field_options
|
51
|
+
@options[:field_options] = {}
|
52
|
+
FIELD_OPTIONS.each do |key|
|
53
|
+
next unless @options.key?(key)
|
54
|
+
@options[:field_options][key] = @options.delete(key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_orderable_scope
|
59
|
+
return unless @options[:scope].class.in?([Array, Symbol, String])
|
60
|
+
|
61
|
+
scope = Array(@options[:scope])
|
62
|
+
scope.map! do |value|
|
63
|
+
case value
|
64
|
+
when Symbol
|
65
|
+
relation = @orderable_class.relations[@options[:scope].to_s]&.key&.to_sym
|
66
|
+
relation || value
|
67
|
+
when String
|
68
|
+
value.to_sym
|
69
|
+
else
|
70
|
+
raise ArgumentError.new("Orderable :scope invalid: #{@options[:scope]}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@options[:scope] = scope
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Configs
|
6
|
+
class GlobalConfig
|
7
|
+
attr_accessor :field,
|
8
|
+
:as,
|
9
|
+
:base,
|
10
|
+
:index,
|
11
|
+
:use_transactions,
|
12
|
+
:transaction_max_retries,
|
13
|
+
:lock_collection
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
self.field = :position
|
17
|
+
self.index = true
|
18
|
+
self.base = 1
|
19
|
+
self.use_transactions = false
|
20
|
+
self.transaction_max_retries = 10
|
21
|
+
self.lock_collection = :mongoid_orderable_locks
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,18 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Errors
|
6
|
+
class InvalidTargetPosition < ::Mongoid::Errors::MongoidError
|
7
|
+
def initialize(value)
|
8
|
+
super _compose_message(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def _compose_message(value)
|
14
|
+
compose_message 'invalid_target_position', value: value.inspect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -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
|