mongoid-slug 6.0.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -20
  3. data/README.md +392 -336
  4. data/lib/mongoid/slug/criteria.rb +111 -107
  5. data/lib/mongoid/slug/{index.rb → index_builder.rb} +69 -45
  6. data/lib/mongoid/slug/railtie.rb +11 -9
  7. data/lib/mongoid/slug/slug_id_strategy.rb +5 -3
  8. data/lib/mongoid/slug/unique_slug.rb +172 -173
  9. data/lib/mongoid/slug/version.rb +7 -5
  10. data/lib/mongoid/slug.rb +333 -328
  11. data/lib/mongoid_slug.rb +4 -2
  12. data/lib/tasks/mongoid_slug.rake +17 -19
  13. metadata +13 -173
  14. data/spec/models/alias.rb +0 -6
  15. data/spec/models/article.rb +0 -9
  16. data/spec/models/artist.rb +0 -8
  17. data/spec/models/artwork.rb +0 -10
  18. data/spec/models/author.rb +0 -15
  19. data/spec/models/author_polymorphic.rb +0 -15
  20. data/spec/models/book.rb +0 -12
  21. data/spec/models/book_polymorphic.rb +0 -12
  22. data/spec/models/caption.rb +0 -17
  23. data/spec/models/entity.rb +0 -11
  24. data/spec/models/friend.rb +0 -7
  25. data/spec/models/incorrect_slug_persistence.rb +0 -9
  26. data/spec/models/integer_id.rb +0 -9
  27. data/spec/models/magazine.rb +0 -7
  28. data/spec/models/page.rb +0 -9
  29. data/spec/models/page_localize.rb +0 -9
  30. data/spec/models/page_slug_localized.rb +0 -9
  31. data/spec/models/page_slug_localized_custom.rb +0 -10
  32. data/spec/models/page_slug_localized_history.rb +0 -9
  33. data/spec/models/partner.rb +0 -7
  34. data/spec/models/person.rb +0 -12
  35. data/spec/models/relationship.rb +0 -8
  36. data/spec/models/string_id.rb +0 -9
  37. data/spec/models/subject.rb +0 -7
  38. data/spec/models/without_slug.rb +0 -5
  39. data/spec/mongoid/criteria_spec.rb +0 -207
  40. data/spec/mongoid/index_spec.rb +0 -33
  41. data/spec/mongoid/slug_spec.rb +0 -1169
  42. data/spec/shared/indexes.rb +0 -41
  43. data/spec/spec_helper.rb +0 -61
  44. data/spec/tasks/mongoid_slug_rake_spec.rb +0 -73
@@ -1,107 +1,111 @@
1
- module Mongoid
2
- module Slug
3
- class Criteria < Mongoid::Criteria
4
- # Find the matching document(s) in the criteria for the provided ids or slugs.
5
- #
6
- # If the document _ids are of the type BSON::ObjectId, and all the supplied parameters are
7
- # convertible to BSON::ObjectId (via BSON::ObjectId#from_string), finding will be
8
- # performed via _ids.
9
- #
10
- # If the document has any other type of _id field, and all the supplied parameters are of the same
11
- # type, finding will be performed via _ids.
12
- #
13
- # Otherwise finding will be performed via slugs.
14
- #
15
- # @example Find by an id.
16
- # criteria.find(BSON::ObjectId.new)
17
- #
18
- # @example Find by multiple ids.
19
- # criteria.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
20
- #
21
- # @example Find by a slug.
22
- # criteria.find('some-slug')
23
- #
24
- # @example Find by multiple slugs.
25
- # criteria.find([ 'some-slug', 'some-other-slug' ])
26
- #
27
- # @param [ Array<Object> ] args The ids or slugs to search for.
28
- #
29
- # @return [ Array<Document>, Document ] The matching document(s).
30
- def find(*args)
31
- look_like_slugs?(args.__find_args__) ? find_by_slug!(*args) : super
32
- end
33
-
34
- # Find the matchind document(s) in the criteria for the provided slugs.
35
- #
36
- # @example Find by a slug.
37
- # criteria.find('some-slug')
38
- #
39
- # @example Find by multiple slugs.
40
- # criteria.find([ 'some-slug', 'some-other-slug' ])
41
- #
42
- # @param [ Array<Object> ] args The slugs to search for.
43
- #
44
- # @return [ Array<Document>, Document ] The matching document(s).
45
- def find_by_slug!(*args)
46
- slugs = args.__find_args__
47
- raise_invalid if slugs.any?(&:nil?)
48
- for_slugs(slugs).execute_or_raise_for_slugs(slugs, args.multi_arged?)
49
- end
50
-
51
- def look_like_slugs?(args)
52
- return false unless args.all? { |id| id.is_a?(String) }
53
- id_field = @klass.fields['_id']
54
- @slug_strategy ||= id_field.options[:slug_id_strategy] || build_slug_strategy(id_field.type)
55
- args.none? { |id| @slug_strategy.call(id) }
56
- end
57
-
58
- protected
59
-
60
- # unless a :slug_id_strategy option is defined on the id field,
61
- # use object_id or string strategy depending on the id_type
62
- # otherwise default for all other id_types
63
- def build_slug_strategy(id_type)
64
- type_method = id_type.to_s.downcase.split('::').last + '_slug_strategy'
65
- respond_to?(type_method, true) ? method(type_method) : ->(_id) { false }
66
- end
67
-
68
- # a string will not look like a slug if it looks like a legal BSON::ObjectId
69
- def objectid_slug_strategy(id)
70
- Mongoid::Compatibility::ObjectId.legal?(id)
71
- end
72
-
73
- # a string will always look like a slug
74
- def string_slug_strategy(_id)
75
- true
76
- end
77
-
78
- def for_slugs(slugs)
79
- # _translations
80
- localized = (begin
81
- @klass.fields['_slugs'].options[:localize]
82
- rescue StandardError
83
- false
84
- end)
85
- if localized
86
- def_loc = I18n.default_locale
87
- query = { '$in' => slugs }
88
- where({ '$or' => [{ _slugs: query }, { "_slugs.#{def_loc}" => query }] }).limit(slugs.length)
89
- else
90
- where(_slugs: { '$in' => slugs }).limit(slugs.length)
91
- end
92
- end
93
-
94
- def execute_or_raise_for_slugs(slugs, multi)
95
- result = uniq
96
- check_for_missing_documents_for_slugs!(result, slugs)
97
- multi ? result : result.first
98
- end
99
-
100
- def check_for_missing_documents_for_slugs!(result, slugs)
101
- missing_slugs = slugs - result.map(&:slugs).flatten
102
- return unless !missing_slugs.blank? && Mongoid.raise_not_found_error
103
- raise Errors::DocumentNotFound.new(klass, slugs, missing_slugs)
104
- end
105
- end
106
- end
107
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Slug
5
+ class Criteria < Mongoid::Criteria
6
+ # Find the matching document(s) in the criteria for the provided ids or slugs.
7
+ #
8
+ # If the document _ids are of the type BSON::ObjectId, and all the supplied parameters are
9
+ # convertible to BSON::ObjectId (via BSON::ObjectId#from_string), finding will be
10
+ # performed via _ids.
11
+ #
12
+ # If the document has any other type of _id field, and all the supplied parameters are of the same
13
+ # type, finding will be performed via _ids.
14
+ #
15
+ # Otherwise finding will be performed via slugs.
16
+ #
17
+ # @example Find by an id.
18
+ # criteria.find(BSON::ObjectId.new)
19
+ #
20
+ # @example Find by multiple ids.
21
+ # criteria.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
22
+ #
23
+ # @example Find by a slug.
24
+ # criteria.find('some-slug')
25
+ #
26
+ # @example Find by multiple slugs.
27
+ # criteria.find([ 'some-slug', 'some-other-slug' ])
28
+ #
29
+ # @param [ Array<Object> ] args The ids or slugs to search for.
30
+ #
31
+ # @return [ Array<Document>, Document ] The matching document(s).
32
+ def find(*args)
33
+ look_like_slugs?(args.__find_args__) ? find_by_slug!(*args) : super
34
+ end
35
+
36
+ # Find the matchind document(s) in the criteria for the provided slugs.
37
+ #
38
+ # @example Find by a slug.
39
+ # criteria.find('some-slug')
40
+ #
41
+ # @example Find by multiple slugs.
42
+ # criteria.find([ 'some-slug', 'some-other-slug' ])
43
+ #
44
+ # @param [ Array<Object> ] args The slugs to search for.
45
+ #
46
+ # @return [ Array<Document>, Document ] The matching document(s).
47
+ def find_by_slug!(*args)
48
+ slugs = args.__find_args__
49
+ raise_invalid if slugs.any?(&:nil?)
50
+ for_slugs(slugs).execute_or_raise_for_slugs(slugs, args.multi_arged?)
51
+ end
52
+
53
+ def look_like_slugs?(args)
54
+ return false unless args.all? { |id| id.is_a?(String) }
55
+
56
+ id_field = @klass.fields['_id']
57
+ @slug_strategy ||= id_field.options[:slug_id_strategy] || build_slug_strategy(id_field.type)
58
+ args.none? { |id| @slug_strategy.call(id) }
59
+ end
60
+
61
+ protected
62
+
63
+ # unless a :slug_id_strategy option is defined on the id field,
64
+ # use object_id or string strategy depending on the id_type
65
+ # otherwise default for all other id_types
66
+ def build_slug_strategy(id_type)
67
+ type_method = "#{id_type.to_s.downcase.split('::').last}_slug_strategy"
68
+ respond_to?(type_method, true) ? method(type_method) : ->(_id) { false }
69
+ end
70
+
71
+ # a string will not look like a slug if it looks like a legal BSON::ObjectId
72
+ def objectid_slug_strategy(id)
73
+ BSON::ObjectId.legal?(id)
74
+ end
75
+
76
+ # a string will always look like a slug
77
+ def string_slug_strategy(_id)
78
+ true
79
+ end
80
+
81
+ def for_slugs(slugs)
82
+ # _translations
83
+ localized = (begin
84
+ @klass.fields['_slugs'].options[:localize]
85
+ rescue StandardError
86
+ false
87
+ end)
88
+ if localized
89
+ def_loc = I18n.default_locale
90
+ query = { '$in' => slugs }
91
+ where({ '$or' => [{ _slugs: query }, { "_slugs.#{def_loc}" => query }] }).limit(slugs.length)
92
+ else
93
+ where(_slugs: { '$in' => slugs }).limit(slugs.length)
94
+ end
95
+ end
96
+
97
+ def execute_or_raise_for_slugs(slugs, multi)
98
+ result = uniq
99
+ check_for_missing_documents_for_slugs!(result, slugs)
100
+ multi ? result : result.first
101
+ end
102
+
103
+ def check_for_missing_documents_for_slugs!(result, slugs)
104
+ missing_slugs = slugs - result.map(&:slugs).flatten
105
+ return unless !missing_slugs.blank? && Mongoid.raise_not_found_error
106
+
107
+ raise Errors::DocumentNotFound.new(klass, slugs, missing_slugs)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,45 +1,69 @@
1
- module Mongoid
2
- module Slug
3
- module Index
4
- # @param [ String or Symbol ] scope_key The optional scope key for the index
5
- # @param [ Boolean ] by_model_type Whether or not
6
- #
7
- # @return [ Array(Hash, Hash) ] the indexable fields and index options.
8
- def self.build_index(scope_key = nil, by_model_type = false)
9
- # The order of field keys is intentional.
10
- # See: http://docs.mongodb.org/manual/core/index-compound/
11
- fields = {}
12
- fields[:_type] = 1 if by_model_type
13
- fields[scope_key] = 1 if scope_key
14
- fields[:_slugs] = 1
15
-
16
- # By design, we use the unique index constraint when possible to enforce slug uniqueness.
17
- # When migrating legacy data to Mongoid slug, the _slugs field may be null on many records,
18
- # hence we set the sparse index option to ignore these from the unique index.
19
- # See: http://docs.mongodb.org/manual/core/index-sparse/
20
- #
21
- # There are three edge cases where the index must not be unique:
22
- #
23
- # 1) Legacy tables with `scope_key`. The sparse indexes on compound keys (scope + _slugs) are
24
- # whenever ANY of the key values are present (e.g. when scope is set and _slugs is unset),
25
- # and collisions will occur when multiple records have the same scope but null slugs.
26
- #
27
- # 2) Single Table Inheritance (`by_model_type`). MongoDB creates indexes on the parent collection,
28
- # irrespective of how STI is defined in Mongoid, i.e. ANY child index will be applied to EVERY child.
29
- # This can cause collisions using various combinations of scopes.
30
- #
31
- # In the future, MongoDB may implement partial indexes or improve sparse index behavior.
32
- # See: https://jira.mongodb.org/browse/SERVER-785
33
- # https://jira.mongodb.org/browse/SERVER-13780
34
- # https://jira.mongodb.org/browse/SERVER-10403
35
- options = {}
36
- unless scope_key || by_model_type
37
- options[:unique] = true
38
- options[:sparse] = true
39
- end
40
-
41
- [fields, options]
42
- end
43
- end
44
- end
45
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Slug
5
+ module IndexBuilder
6
+ extend self
7
+
8
+ # Creates indexes on a document for a given slug scope
9
+ #
10
+ # @param [ Mongoid::Document ] doc The document on which to create the index(es)
11
+ # @param [ String or Symbol ] scope_key The optional scope key for the index(es)
12
+ # @param [ Boolean ] by_model_type Whether or not to use single table inheritance
13
+ # @param [ Boolean or Array ] localize The locale for localized index field
14
+ #
15
+ # @return [ Array(Hash, Hash) ] the indexable fields and index options.
16
+ def build_indexes(doc, scope_key = nil, by_model_type = false, locales = nil)
17
+ if locales.is_a?(Array)
18
+ locales.each { |locale| build_index(doc, scope_key, by_model_type, locale) }
19
+ else
20
+ build_index(doc, scope_key, by_model_type, locales)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def build_index(doc, scope_key = nil, by_model_type = false, locale = nil)
27
+ # The order of field keys is intentional.
28
+ # See: http://docs.mongodb.org/manual/core/index-compound/
29
+ fields = {}
30
+ fields[:_type] = 1 if by_model_type
31
+ fields[scope_key] = 1 if scope_key
32
+
33
+ locale = ::I18n.default_locale if locale.is_a?(TrueClass)
34
+ if locale
35
+ fields[:"_slugs.#{locale}"] = 1
36
+ else
37
+ fields[:_slugs] = 1
38
+ end
39
+
40
+ # By design, we use the unique index constraint when possible to enforce slug uniqueness.
41
+ # When migrating legacy data to Mongoid slug, the _slugs field may be null on many records,
42
+ # hence we set the sparse index option to ignore these from the unique index.
43
+ # See: http://docs.mongodb.org/manual/core/index-sparse/
44
+ #
45
+ # There are three edge cases where the index must not be unique:
46
+ #
47
+ # 1) Legacy tables with `scope_key`. The sparse indexes on compound keys (scope + _slugs) are
48
+ # whenever ANY of the key values are present (e.g. when scope is set and _slugs is unset),
49
+ # and collisions will occur when multiple records have the same scope but null slugs.
50
+ #
51
+ # 2) Single Table Inheritance (`by_model_type`). MongoDB creates indexes on the parent collection,
52
+ # irrespective of how STI is defined in Mongoid, i.e. ANY child index will be applied to EVERY child.
53
+ # This can cause collisions using various combinations of scopes.
54
+ #
55
+ # In the future, MongoDB may implement partial indexes or improve sparse index behavior.
56
+ # See: https://jira.mongodb.org/browse/SERVER-785
57
+ # https://jira.mongodb.org/browse/SERVER-13780
58
+ # https://jira.mongodb.org/browse/SERVER-10403
59
+ options = {}
60
+ unless scope_key || by_model_type
61
+ options[:unique] = true
62
+ options[:sparse] = true
63
+ end
64
+
65
+ doc.index(fields, options)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,9 +1,11 @@
1
- module Mongoid
2
- module Slug
3
- class Railtie < Rails::Railtie
4
- rake_tasks do
5
- Dir[File.join(File.dirname(__FILE__), '../../tasks/*.rake')].each { |f| load f }
6
- end
7
- end
8
- end
9
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Slug
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ Dir[File.join(File.dirname(__FILE__), '../../tasks/*.rake')].each { |f| load f }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,5 @@
1
- Mongoid::Fields.option(:slug_id_strategy) do |_model, field, value|
2
- field.options[:slug_id_strategy] = value
3
- end
1
+ # frozen_string_literal: true
2
+
3
+ Mongoid::Fields.option(:slug_id_strategy) do |_model, field, value|
4
+ field.options[:slug_id_strategy] = value
5
+ end