mongoid_orderable 5.2.0 → 6.0.0

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