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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +73 -58
  3. data/LICENSE.txt +20 -20
  4. data/README.md +256 -150
  5. data/Rakefile +24 -21
  6. data/lib/config/locales/en.yml +12 -9
  7. data/lib/mongoid/orderable.rb +29 -20
  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/engine.rb +204 -0
  11. data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -15
  12. data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
  13. data/lib/mongoid/orderable/generators/base.rb +21 -0
  14. data/lib/mongoid/orderable/generators/helpers.rb +29 -0
  15. data/lib/mongoid/orderable/generators/listable.rb +41 -0
  16. data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
  17. data/lib/mongoid/orderable/generators/movable.rb +62 -0
  18. data/lib/mongoid/orderable/generators/position.rb +26 -0
  19. data/lib/mongoid/orderable/generators/scope.rb +26 -0
  20. data/lib/mongoid/orderable/installer.rb +63 -0
  21. data/lib/mongoid/orderable/mixins/callbacks.rb +29 -0
  22. data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
  23. data/lib/mongoid/orderable/mixins/listable.rb +49 -0
  24. data/lib/mongoid/orderable/mixins/movable.rb +60 -0
  25. data/lib/mongoid/orderable/version.rb +7 -0
  26. data/lib/mongoid_orderable.rb +29 -56
  27. data/spec/mongoid/orderable_spec.rb +1486 -1380
  28. data/spec/spec_helper.rb +21 -21
  29. metadata +44 -41
  30. data/.gitignore +0 -4
  31. data/.rspec +0 -2
  32. data/.rubocop.yml +0 -6
  33. data/.rubocop_todo.yml +0 -88
  34. data/.rvmrc +0 -1
  35. data/.travis.yml +0 -48
  36. data/CONTRIBUTING.md +0 -118
  37. data/Dangerfile +0 -1
  38. data/Gemfile +0 -26
  39. data/RELEASING.md +0 -68
  40. data/lib/mongoid/orderable/callbacks.rb +0 -79
  41. data/lib/mongoid/orderable/configuration.rb +0 -58
  42. data/lib/mongoid/orderable/errors.rb +0 -2
  43. data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -6
  44. data/lib/mongoid/orderable/generator.rb +0 -33
  45. data/lib/mongoid/orderable/generator/helpers.rb +0 -27
  46. data/lib/mongoid/orderable/generator/listable.rb +0 -39
  47. data/lib/mongoid/orderable/generator/movable.rb +0 -60
  48. data/lib/mongoid/orderable/generator/position.rb +0 -24
  49. data/lib/mongoid/orderable/generator/scope.rb +0 -17
  50. data/lib/mongoid/orderable/helpers.rb +0 -49
  51. data/lib/mongoid/orderable/listable.rb +0 -47
  52. data/lib/mongoid/orderable/movable.rb +0 -56
  53. data/lib/mongoid/orderable/orderable_class.rb +0 -47
  54. data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
  55. data/lib/mongoid_orderable/version.rb +0 -3
  56. 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