mongoid_orderable 5.0.0 → 6.0.2

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