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.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +83 -22
  3. data/LICENSE.txt +20 -0
  4. data/README.md +256 -149
  5. data/Rakefile +24 -6
  6. data/lib/config/locales/en.yml +12 -9
  7. data/lib/mongoid/orderable.rb +29 -22
  8. data/lib/mongoid/orderable/configs/field_config.rb +79 -0
  9. data/lib/mongoid/orderable/configs/global_config.rb +26 -0
  10. data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -18
  11. data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
  12. data/lib/mongoid/orderable/generators/base.rb +21 -0
  13. data/lib/mongoid/orderable/generators/helpers.rb +29 -0
  14. data/lib/mongoid/orderable/generators/listable.rb +41 -0
  15. data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
  16. data/lib/mongoid/orderable/generators/movable.rb +62 -0
  17. data/lib/mongoid/orderable/generators/position.rb +26 -0
  18. data/lib/mongoid/orderable/generators/scope.rb +26 -0
  19. data/lib/mongoid/orderable/handlers/base.rb +167 -0
  20. data/lib/mongoid/orderable/handlers/document.rb +24 -0
  21. data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -0
  22. data/lib/mongoid/orderable/handlers/document_transactional.rb +35 -0
  23. data/lib/mongoid/orderable/handlers/transaction.rb +71 -0
  24. data/lib/mongoid/orderable/installer.rb +63 -0
  25. data/lib/mongoid/orderable/mixins/callbacks.rb +43 -0
  26. data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
  27. data/lib/mongoid/orderable/mixins/listable.rb +49 -0
  28. data/lib/mongoid/orderable/mixins/movable.rb +60 -0
  29. data/lib/mongoid/orderable/version.rb +7 -0
  30. data/lib/mongoid_orderable.rb +33 -54
  31. data/spec/integration/concurrency_spec.rb +232 -0
  32. data/spec/integration/customized_spec.rb +31 -0
  33. data/spec/integration/embedded_spec.rb +41 -0
  34. data/spec/integration/foreign_key_spec.rb +33 -0
  35. data/spec/integration/inherited_spec.rb +54 -0
  36. data/spec/integration/multiple_fields_spec.rb +554 -0
  37. data/spec/integration/multiple_scoped_spec.rb +63 -0
  38. data/spec/integration/no_indexed_spec.rb +23 -0
  39. data/spec/integration/scoped_spec.rb +151 -0
  40. data/spec/integration/simple_spec.rb +184 -0
  41. data/spec/integration/string_scoped_spec.rb +28 -0
  42. data/spec/integration/zero_based_spec.rb +161 -0
  43. data/spec/spec_helper.rb +42 -30
  44. data/spec/support/models.rb +122 -0
  45. metadata +75 -41
  46. data/.gitignore +0 -4
  47. data/.rspec +0 -2
  48. data/.rvmrc +0 -1
  49. data/.travis.yml +0 -14
  50. data/Gemfile +0 -18
  51. data/lib/mongoid/orderable/callbacks.rb +0 -75
  52. data/lib/mongoid/orderable/configuration.rb +0 -60
  53. data/lib/mongoid/orderable/errors.rb +0 -2
  54. data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -14
  55. data/lib/mongoid/orderable/generator.rb +0 -34
  56. data/lib/mongoid/orderable/generator/helpers.rb +0 -29
  57. data/lib/mongoid/orderable/generator/listable.rb +0 -41
  58. data/lib/mongoid/orderable/generator/movable.rb +0 -62
  59. data/lib/mongoid/orderable/generator/position.rb +0 -26
  60. data/lib/mongoid/orderable/generator/scope.rb +0 -17
  61. data/lib/mongoid/orderable/helpers.rb +0 -50
  62. data/lib/mongoid/orderable/listable.rb +0 -49
  63. data/lib/mongoid/orderable/movable.rb +0 -58
  64. data/lib/mongoid/orderable/orderable_class.rb +0 -51
  65. data/lib/mongoid_orderable/mongoid/contexts/enumerable.rb +0 -15
  66. data/lib/mongoid_orderable/mongoid/contexts/mongo.rb +0 -18
  67. data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
  68. data/lib/mongoid_orderable/mongoid/criteria.rb +0 -4
  69. data/lib/mongoid_orderable/version.rb +0 -3
  70. data/mongoid_orderable.gemspec +0 -26
  71. data/spec/mongoid/orderable_spec.rb +0 -1413
data/Rakefile CHANGED
@@ -1,6 +1,24 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => [ :spec ]
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]
@@ -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
- :top, :bottom, or nil."
9
- resolution: "You must give an acceptable value for target position."
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."
@@ -1,22 +1,29 @@
1
- module Mongoid::Orderable
2
- extend ActiveSupport::Concern
3
-
4
- included do
5
- include Mongoid::Orderable::Helpers
6
- include Mongoid::Orderable::Callbacks
7
- include Mongoid::Orderable::Movable
8
- include Mongoid::Orderable::Listable
9
-
10
- class_attribute :orderable_configurations
11
- end
12
-
13
- module ClassMethods
14
-
15
- def orderable options = {}
16
- configuration = Mongoid::Orderable::Configuration.build(self, options)
17
-
18
- Mongoid::Orderable::OrderableClass.setup(self, configuration)
19
- end
20
-
21
- end
22
- end
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
- module Mongoid::Orderable
2
- module Errors
3
- class InvalidTargetPosition < Mongoid::Orderable::Errors::MongoidOrderableError
4
- def initialize value
5
- super _compose_message(value)
6
- end
7
-
8
- private
9
- def _compose_message value
10
- if ::Mongoid::Compatibility::Version.mongoid2?
11
- translate 'invalid_target_position', { :value => value.inspect }
12
- else
13
- compose_message 'invalid_target_position', { :value => value.inspect }
14
- end
15
- end
16
- end
17
- end
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