mongoid-slug 7.0.0 → 7.1.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.
@@ -1,111 +1,120 @@
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
+ # 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> ] slugs The ids or slugs to search for.
30
+ #
31
+ # @return [ Array<Document>, Document ] The matching document(s).
32
+ def find(*slugs)
33
+ return find_by_slug!(*slugs) if look_like_slugs?(*slugs)
34
+
35
+ super
36
+ end
37
+
38
+ # Find the matchind document(s) in the criteria for the provided slugs.
39
+ #
40
+ # @example Find by a slug.
41
+ # criteria.find('some-slug')
42
+ #
43
+ # @example Find by multiple slugs.
44
+ # criteria.find([ 'some-slug', 'some-other-slug' ])
45
+ #
46
+ # @param [ Array<Object>... ] slugs The slugs to search for.
47
+ #
48
+ # @return [ Array<Document>, Document ] The matching document(s).
49
+ def find_by_slug!(*slugs)
50
+ slugs = find_args(slugs)
51
+ raise_invalid if slugs.any?(&:nil?)
52
+ for_slugs(slugs).execute_or_raise_for_slugs(slugs)
53
+ end
54
+
55
+ def look_like_slugs?(*slugs)
56
+ slugs = find_args(slugs)
57
+ return false unless slugs.all?(String)
58
+
59
+ id_field = @klass.fields['_id']
60
+ @slug_strategy ||= id_field.options[:slug_id_strategy] || build_slug_strategy(id_field.type)
61
+ slugs.none? { |slug| @slug_strategy.call(slug) }
62
+ end
63
+
64
+ protected
65
+
66
+ # unless a :slug_id_strategy option is defined on the id field,
67
+ # use object_id or string strategy depending on the id_type
68
+ # otherwise default for all other id_types
69
+ def build_slug_strategy(id_type)
70
+ type_method = "#{id_type.to_s.downcase.split('::').last}_slug_strategy"
71
+ respond_to?(type_method, true) ? method(type_method) : ->(_id) { false }
72
+ end
73
+
74
+ # a string will not look like a slug if it looks like a legal BSON::ObjectId
75
+ def objectid_slug_strategy(id)
76
+ BSON::ObjectId.legal?(id)
77
+ end
78
+
79
+ # a string will always look like a slug
80
+ def string_slug_strategy(_id)
81
+ true
82
+ end
83
+
84
+ def for_slugs(slugs)
85
+ # _translations
86
+ localized = (begin
87
+ @klass.fields['_slugs'].options[:localize]
88
+ rescue StandardError
89
+ false
90
+ end)
91
+ if localized
92
+ def_loc = I18n.default_locale
93
+ query = { '$in' => slugs }
94
+ where({ '$or' => [{ _slugs: query }, { "_slugs.#{def_loc}" => query }] }).limit(slugs.length)
95
+ else
96
+ where(_slugs: { '$in' => slugs }).limit(slugs.length)
97
+ end
98
+ end
99
+
100
+ def find_args(args)
101
+ args = args.flatten
102
+ args.uniq!(&:to_s)
103
+ args
104
+ end
105
+
106
+ def execute_or_raise_for_slugs(slugs)
107
+ result = uniq
108
+ check_for_missing_documents_for_slugs!(result, slugs)
109
+ slugs.size == 1 ? result.first : result
110
+ end
111
+
112
+ def check_for_missing_documents_for_slugs!(result, slugs)
113
+ missing_slugs = slugs - result.map(&:slugs).flatten
114
+ return unless !missing_slugs.blank? && Mongoid.raise_not_found_error
115
+
116
+ raise Errors::DocumentNotFound.new(klass, slugs, missing_slugs)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,69 +1,70 @@
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
+ # 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 or Array<String, 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
+
32
+ Array(scope_key).each { |key| fields[key] = 1 }
33
+
34
+ locale = ::I18n.default_locale if locale.is_a?(TrueClass)
35
+ if locale
36
+ fields[:"_slugs.#{locale}"] = 1
37
+ else
38
+ fields[:_slugs] = 1
39
+ end
40
+
41
+ # By design, we use the unique index constraint when possible to enforce slug uniqueness.
42
+ # When migrating legacy data to Mongoid slug, the _slugs field may be null on many records,
43
+ # hence we set the sparse index option to ignore these from the unique index.
44
+ # See: http://docs.mongodb.org/manual/core/index-sparse/
45
+ #
46
+ # There are three edge cases where the index must not be unique:
47
+ #
48
+ # 1) Legacy tables with `scope_key`. The sparse indexes on compound keys (scope + _slugs) are
49
+ # whenever ANY of the key values are present (e.g. when scope is set and _slugs is unset),
50
+ # and collisions will occur when multiple records have the same scope but null slugs.
51
+ #
52
+ # 2) Single Table Inheritance (`by_model_type`). MongoDB creates indexes on the parent collection,
53
+ # irrespective of how STI is defined in Mongoid, i.e. ANY child index will be applied to EVERY child.
54
+ # This can cause collisions using various combinations of scopes.
55
+ #
56
+ # In the future, MongoDB may implement partial indexes or improve sparse index behavior.
57
+ # See: https://jira.mongodb.org/browse/SERVER-785
58
+ # https://jira.mongodb.org/browse/SERVER-13780
59
+ # https://jira.mongodb.org/browse/SERVER-10403
60
+ options = {}
61
+ unless scope_key || by_model_type
62
+ options[:unique] = true
63
+ options[:sparse] = true
64
+ end
65
+
66
+ doc.index(fields, options)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,11 +1,11 @@
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
+ # 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,5 +1,5 @@
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
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