mongoid-slug 6.0.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -20
  3. data/README.md +361 -336
  4. data/lib/mongoid/slug.rb +328 -328
  5. data/lib/mongoid/slug/criteria.rb +107 -107
  6. data/lib/mongoid/slug/{index.rb → index_builder.rb} +67 -45
  7. data/lib/mongoid/slug/railtie.rb +9 -9
  8. data/lib/mongoid/slug/slug_id_strategy.rb +3 -3
  9. data/lib/mongoid/slug/unique_slug.rb +173 -173
  10. data/lib/mongoid/slug/version.rb +5 -5
  11. data/lib/mongoid_slug.rb +2 -2
  12. data/lib/tasks/mongoid_slug.rake +15 -19
  13. data/spec/models/alias.rb +6 -6
  14. data/spec/models/article.rb +9 -9
  15. data/spec/models/artist.rb +8 -8
  16. data/spec/models/artwork.rb +10 -10
  17. data/spec/models/author.rb +15 -15
  18. data/spec/models/author_polymorphic.rb +15 -15
  19. data/spec/models/book.rb +12 -12
  20. data/spec/models/book_polymorphic.rb +12 -12
  21. data/spec/models/caption.rb +17 -17
  22. data/spec/models/entity.rb +11 -11
  23. data/spec/models/friend.rb +7 -7
  24. data/spec/models/incorrect_slug_persistence.rb +9 -9
  25. data/spec/models/integer_id.rb +9 -9
  26. data/spec/models/magazine.rb +7 -7
  27. data/spec/models/page.rb +9 -9
  28. data/spec/models/page_localize.rb +9 -9
  29. data/spec/models/page_slug_localized.rb +9 -9
  30. data/spec/models/page_slug_localized_custom.rb +10 -10
  31. data/spec/models/page_slug_localized_history.rb +9 -9
  32. data/spec/models/partner.rb +7 -7
  33. data/spec/models/person.rb +12 -12
  34. data/spec/models/relationship.rb +8 -8
  35. data/spec/models/string_id.rb +9 -9
  36. data/spec/models/subject.rb +7 -7
  37. data/spec/models/without_slug.rb +5 -5
  38. data/spec/mongoid/criteria_spec.rb +207 -207
  39. data/spec/mongoid/index_builder_spec.rb +105 -0
  40. data/spec/mongoid/slug_spec.rb +1175 -1169
  41. data/spec/shared/indexes.rb +41 -41
  42. data/spec/spec_helper.rb +61 -61
  43. data/spec/tasks/mongoid_slug_rake_spec.rb +73 -73
  44. metadata +31 -32
  45. data/spec/mongoid/index_spec.rb +0 -33
data/lib/mongoid/slug.rb CHANGED
@@ -1,328 +1,328 @@
1
- require 'mongoid'
2
- require 'stringex'
3
- require 'mongoid/slug/criteria'
4
- require 'mongoid/slug/index'
5
- require 'mongoid/slug/unique_slug'
6
- require 'mongoid/slug/slug_id_strategy'
7
- require 'mongoid-compatibility'
8
- require 'mongoid/slug/railtie' if defined?(Rails)
9
-
10
- module Mongoid
11
- # Slugs your Mongoid model.
12
- module Slug
13
- extend ActiveSupport::Concern
14
-
15
- MONGO_INDEX_KEY_LIMIT_BYTES = 1024
16
-
17
- included do
18
- cattr_accessor :slug_reserved_words,
19
- :slug_scope,
20
- :slugged_attributes,
21
- :slug_url_builder,
22
- :slug_history,
23
- :slug_by_model_type,
24
- :slug_max_length
25
-
26
- # field :_slugs, type: Array, default: [], localize: false
27
- # alias_attribute :slugs, :_slugs
28
- end
29
-
30
- class << self
31
- attr_accessor :default_slug
32
- def configure(&block)
33
- instance_eval(&block)
34
- end
35
-
36
- def slug(&block)
37
- @default_slug = block if block_given?
38
- end
39
- end
40
-
41
- module ClassMethods
42
- # @overload slug(*fields)
43
- # Sets one ore more fields as source of slug.
44
- # @param [Array] fields One or more fields the slug should be based on.
45
- # @yield If given, the block is used to build a custom slug.
46
- #
47
- # @overload slug(*fields, options)
48
- # Sets one ore more fields as source of slug.
49
- # @param [Array] fields One or more fields the slug should be based on.
50
- # @param [Hash] options
51
- # @param options [Boolean] :history Whether a history of changes to
52
- # the slug should be retained. When searched by slug, the document now
53
- # matches both past and present slugs.
54
- # @param options [Boolean] :permanent Whether the slug should be
55
- # immutable. Defaults to `false`.
56
- # @param options [Array] :reserve` A list of reserved slugs
57
- # @param options :scope [Symbol] a reference association or field to
58
- # scope the slug by. Embedded documents are, by default, scoped by
59
- # their parent.
60
- # @param options :max_length [Integer] the maximum length of the text portion of the slug
61
- # @yield If given, a block is used to build a slug.
62
- #
63
- # @example A custom builder
64
- # class Person
65
- # include Mongoid::Document
66
- # include Mongoid::Slug
67
- #
68
- # field :names, :type => Array
69
- # slug :names do |doc|
70
- # doc.names.join(' ')
71
- # end
72
- # end
73
- #
74
- def slug(*fields, &block)
75
- options = fields.extract_options!
76
-
77
- self.slug_scope = options[:scope]
78
- self.slug_reserved_words = options[:reserve] || Set.new(%w[new edit])
79
- self.slugged_attributes = fields.map(&:to_s)
80
- self.slug_history = options[:history]
81
- self.slug_by_model_type = options[:by_model_type]
82
- self.slug_max_length = options.key?(:max_length) ? options[:max_length] : MONGO_INDEX_KEY_LIMIT_BYTES - 32
83
-
84
- field :_slugs, type: Array, localize: options[:localize]
85
- alias_attribute :slugs, :_slugs
86
-
87
- # Set index
88
- index(*Mongoid::Slug::Index.build_index(slug_scope_key, slug_by_model_type)) unless embedded?
89
-
90
- self.slug_url_builder = block_given? ? block : default_slug_url_builder
91
-
92
- #-- always create slug on create
93
- #-- do not create new slug on update if the slug is permanent
94
- if options[:permanent]
95
- set_callback :create, :before, :build_slug
96
- else
97
- set_callback :save, :before, :build_slug, if: :slug_should_be_rebuilt?
98
- end
99
- end
100
-
101
- def default_slug_url_builder
102
- Mongoid::Slug.default_slug || ->(cur_object) { cur_object.slug_builder.to_url }
103
- end
104
-
105
- def look_like_slugs?(*args)
106
- with_default_scope.look_like_slugs?(*args)
107
- end
108
-
109
- # Returns the scope key for indexing, considering associations
110
- #
111
- # @return [ Array<Document>, Document ]
112
- def slug_scope_key
113
- return nil unless slug_scope
114
- reflect_on_association(slug_scope).try(:key) || slug_scope
115
- end
116
-
117
- # Find documents by slugs.
118
- #
119
- # A document matches if any of its slugs match one of the supplied params.
120
- #
121
- # A document matching multiple supplied params will be returned only once.
122
- #
123
- # If any supplied param does not match a document a Mongoid::Errors::DocumentNotFound will be raised.
124
- #
125
- # @example Find by a slug.
126
- # Model.find_by_slug!('some-slug')
127
- #
128
- # @example Find by multiple slugs.
129
- # Model.find_by_slug!('some-slug', 'some-other-slug')
130
- #
131
- # @param [ Array<Object> ] args The slugs to search for.
132
- #
133
- # @return [ Array<Document>, Document ] The matching document(s).
134
- def find_by_slug!(*args)
135
- with_default_scope.find_by_slug!(*args)
136
- end
137
-
138
- def queryable
139
- current_scope || Criteria.new(self) # Use Mongoid::Slug::Criteria for slugged documents.
140
- end
141
-
142
- private
143
-
144
- if Mongoid::Compatibility::Version.mongoid5_or_newer? && Threaded.method(:current_scope).arity == -1
145
- def current_scope
146
- Threaded.current_scope(self)
147
- end
148
- elsif Mongoid::Compatibility::Version.mongoid5_or_newer?
149
- def current_scope
150
- Threaded.current_scope
151
- end
152
- else
153
- def current_scope
154
- scope_stack.last
155
- end
156
- end
157
- end
158
-
159
- # Builds a new slug.
160
- #
161
- # @return [true]
162
- def build_slug
163
- if localized?
164
- begin
165
- orig_locale = I18n.locale
166
- all_locales.each do |target_locale|
167
- I18n.locale = target_locale
168
- apply_slug
169
- end
170
- ensure
171
- I18n.locale = orig_locale
172
- end
173
- else
174
- apply_slug
175
- end
176
- true
177
- end
178
-
179
- def apply_slug
180
- new_slug = find_unique_slug
181
-
182
- # skip slug generation and use Mongoid id
183
- # to find document instead
184
- return true if new_slug.size.zero?
185
-
186
- # avoid duplicate slugs
187
- _slugs.delete(new_slug) if _slugs
188
-
189
- if !!slug_history && _slugs.is_a?(Array)
190
- append_slug(new_slug)
191
- else
192
- self._slugs = [new_slug]
193
- end
194
- end
195
-
196
- # Builds slug then atomically sets it in the database.
197
- #
198
- # This method is adapted to use the :set method variants from both
199
- # Mongoid 3 (two args) and Mongoid 4 (hash arg)
200
- def set_slug!
201
- build_slug
202
- method(:set).arity == 1 ? set(_slugs: _slugs) : set(:_slugs, _slugs)
203
- end
204
-
205
- # Atomically unsets the slug field in the database. It is important to unset
206
- # the field for the sparse index on slugs.
207
- #
208
- # This also resets the in-memory value of the slug field to its default (empty array)
209
- def unset_slug!
210
- unset(:_slugs)
211
- clear_slug!
212
- end
213
-
214
- # Rolls back the slug value from the Mongoid changeset.
215
- def reset_slug!
216
- reset__slugs!
217
- end
218
-
219
- # Sets the slug to its default value.
220
- def clear_slug!
221
- self._slugs = []
222
- end
223
-
224
- # Finds a unique slug, were specified string used to generate a slug.
225
- #
226
- # Returned slug will the same as the specified string when there are no
227
- # duplicates.
228
- #
229
- # @return [String] A unique slug
230
- def find_unique_slug
231
- UniqueSlug.new(self).find_unique
232
- end
233
-
234
- # @return [Boolean] Whether the slug requires to be rebuilt
235
- def slug_should_be_rebuilt?
236
- new_record? || _slugs_changed? || slugged_attributes_changed?
237
- end
238
-
239
- def slugged_attributes_changed?
240
- slugged_attributes.any? { |f| attribute_changed? f.to_s }
241
- end
242
-
243
- # @return [String] A string which Action Pack uses for constructing an URL
244
- # to this record.
245
- def to_param
246
- slug || super
247
- end
248
-
249
- # @return [String] the slug, or nil if the document does not have a slug.
250
- def slug
251
- return _slugs.last if _slugs
252
- _id.to_s
253
- end
254
-
255
- def slug_builder
256
- cur_slug = nil
257
- if new_with_slugs? || persisted_with_slug_changes?
258
- # user defined slug
259
- cur_slug = _slugs.last
260
- end
261
- # generate slug if the slug is not user defined or does not exist
262
- cur_slug || pre_slug_string
263
- end
264
-
265
- private
266
-
267
- def append_slug(value)
268
- if localized?
269
- # This is necessary for the scenario in which the slugged locale is not yet present
270
- # but the default locale is. In this situation, self._slugs falls back to the default
271
- # which is undesired
272
- current_slugs = _slugs_translations.fetch(I18n.locale.to_s, [])
273
- current_slugs << value
274
- self._slugs_translations = _slugs_translations.merge(I18n.locale.to_s => current_slugs)
275
- else
276
- _slugs << value
277
- end
278
- end
279
-
280
- # Returns true if object is a new record and slugs are present
281
- def new_with_slugs?
282
- if localized?
283
- # We need to check if slugs are present for the locale without falling back
284
- # to a default
285
- new_record? && _slugs_translations.fetch(I18n.locale.to_s, []).any?
286
- else
287
- new_record? && _slugs.present?
288
- end
289
- end
290
-
291
- # Returns true if object has been persisted and has changes in the slug
292
- def persisted_with_slug_changes?
293
- if localized?
294
- changes = _slugs_change
295
- return (persisted? && false) if changes.nil?
296
-
297
- # ensure we check for changes only between the same locale
298
- original = changes.first.try(:fetch, I18n.locale.to_s, nil)
299
- compare = changes.last.try(:fetch, I18n.locale.to_s, nil)
300
- persisted? && original != compare
301
- else
302
- persisted? && _slugs_changed?
303
- end
304
- end
305
-
306
- def localized?
307
- fields['_slugs'].options[:localize]
308
- rescue StandardError
309
- false
310
- end
311
-
312
- # Return all possible locales for model
313
- # Avoiding usage of I18n.available_locales in case the user hasn't set it properly, or is
314
- # doing something crazy, but at the same time we need a fallback in case the model doesn't
315
- # have any localized attributes at all (extreme edge case).
316
- def all_locales
317
- locales = slugged_attributes
318
- .map { |attr| send("#{attr}_translations").keys if respond_to?("#{attr}_translations") }
319
- .flatten.compact.uniq
320
- locales = I18n.available_locales if locales.empty?
321
- locales
322
- end
323
-
324
- def pre_slug_string
325
- slugged_attributes.map { |f| send f }.join ' '
326
- end
327
- end
328
- end
1
+ require 'mongoid'
2
+ require 'stringex'
3
+ require 'mongoid/slug/criteria'
4
+ require 'mongoid/slug/index_builder'
5
+ require 'mongoid/slug/unique_slug'
6
+ require 'mongoid/slug/slug_id_strategy'
7
+ require 'mongoid-compatibility'
8
+ require 'mongoid/slug/railtie' if defined?(Rails)
9
+
10
+ module Mongoid
11
+ # Slugs your Mongoid model.
12
+ module Slug
13
+ extend ActiveSupport::Concern
14
+
15
+ MONGO_INDEX_KEY_LIMIT_BYTES = 1024
16
+
17
+ included do
18
+ cattr_accessor :slug_reserved_words,
19
+ :slug_scope,
20
+ :slugged_attributes,
21
+ :slug_url_builder,
22
+ :slug_history,
23
+ :slug_by_model_type,
24
+ :slug_max_length
25
+
26
+ # field :_slugs, type: Array, default: [], localize: false
27
+ # alias_attribute :slugs, :_slugs
28
+ end
29
+
30
+ class << self
31
+ attr_accessor :default_slug
32
+ def configure(&block)
33
+ instance_eval(&block)
34
+ end
35
+
36
+ def slug(&block)
37
+ @default_slug = block if block_given?
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+ # @overload slug(*fields)
43
+ # Sets one ore more fields as source of slug.
44
+ # @param [Array] fields One or more fields the slug should be based on.
45
+ # @yield If given, the block is used to build a custom slug.
46
+ #
47
+ # @overload slug(*fields, options)
48
+ # Sets one ore more fields as source of slug.
49
+ # @param [Array] fields One or more fields the slug should be based on.
50
+ # @param [Hash] options
51
+ # @param options [Boolean] :history Whether a history of changes to
52
+ # the slug should be retained. When searched by slug, the document now
53
+ # matches both past and present slugs.
54
+ # @param options [Boolean] :permanent Whether the slug should be
55
+ # immutable. Defaults to `false`.
56
+ # @param options [Array] :reserve` A list of reserved slugs
57
+ # @param options :scope [Symbol] a reference association or field to
58
+ # scope the slug by. Embedded documents are, by default, scoped by
59
+ # their parent.
60
+ # @param options :max_length [Integer] the maximum length of the text portion of the slug
61
+ # @yield If given, a block is used to build a slug.
62
+ #
63
+ # @example A custom builder
64
+ # class Person
65
+ # include Mongoid::Document
66
+ # include Mongoid::Slug
67
+ #
68
+ # field :names, :type => Array
69
+ # slug :names do |doc|
70
+ # doc.names.join(' ')
71
+ # end
72
+ # end
73
+ #
74
+ def slug(*fields, &block)
75
+ options = fields.extract_options!
76
+
77
+ self.slug_scope = options[:scope]
78
+ self.slug_reserved_words = options[:reserve] || Set.new(%w[new edit])
79
+ self.slugged_attributes = fields.map(&:to_s)
80
+ self.slug_history = options[:history]
81
+ self.slug_by_model_type = options[:by_model_type]
82
+ self.slug_max_length = options.key?(:max_length) ? options[:max_length] : MONGO_INDEX_KEY_LIMIT_BYTES - 32
83
+
84
+ field :_slugs, type: Array, localize: options[:localize]
85
+ alias_attribute :slugs, :_slugs
86
+
87
+ # Set indexes
88
+ Mongoid::Slug::IndexBuilder.build_indexes(self, slug_scope_key, slug_by_model_type, options[:localize]) unless embedded?
89
+
90
+ self.slug_url_builder = block_given? ? block : default_slug_url_builder
91
+
92
+ #-- always create slug on create
93
+ #-- do not create new slug on update if the slug is permanent
94
+ if options[:permanent]
95
+ set_callback :create, :before, :build_slug
96
+ else
97
+ set_callback :save, :before, :build_slug, if: :slug_should_be_rebuilt?
98
+ end
99
+ end
100
+
101
+ def default_slug_url_builder
102
+ Mongoid::Slug.default_slug || ->(cur_object) { cur_object.slug_builder.to_url }
103
+ end
104
+
105
+ def look_like_slugs?(*args)
106
+ with_default_scope.look_like_slugs?(*args)
107
+ end
108
+
109
+ # Returns the scope key for indexing, considering associations
110
+ #
111
+ # @return [ Array<Document>, Document ]
112
+ def slug_scope_key
113
+ return nil unless slug_scope
114
+ reflect_on_association(slug_scope).try(:key) || slug_scope
115
+ end
116
+
117
+ # Find documents by slugs.
118
+ #
119
+ # A document matches if any of its slugs match one of the supplied params.
120
+ #
121
+ # A document matching multiple supplied params will be returned only once.
122
+ #
123
+ # If any supplied param does not match a document a Mongoid::Errors::DocumentNotFound will be raised.
124
+ #
125
+ # @example Find by a slug.
126
+ # Model.find_by_slug!('some-slug')
127
+ #
128
+ # @example Find by multiple slugs.
129
+ # Model.find_by_slug!('some-slug', 'some-other-slug')
130
+ #
131
+ # @param [ Array<Object> ] args The slugs to search for.
132
+ #
133
+ # @return [ Array<Document>, Document ] The matching document(s).
134
+ def find_by_slug!(*args)
135
+ with_default_scope.find_by_slug!(*args)
136
+ end
137
+
138
+ def queryable
139
+ current_scope || Criteria.new(self) # Use Mongoid::Slug::Criteria for slugged documents.
140
+ end
141
+
142
+ private
143
+
144
+ if Mongoid::Compatibility::Version.mongoid5_or_newer? && Threaded.method(:current_scope).arity == -1
145
+ def current_scope
146
+ Threaded.current_scope(self)
147
+ end
148
+ elsif Mongoid::Compatibility::Version.mongoid5_or_newer?
149
+ def current_scope
150
+ Threaded.current_scope
151
+ end
152
+ else
153
+ def current_scope
154
+ scope_stack.last
155
+ end
156
+ end
157
+ end
158
+
159
+ # Builds a new slug.
160
+ #
161
+ # @return [true]
162
+ def build_slug
163
+ if localized?
164
+ begin
165
+ orig_locale = I18n.locale
166
+ all_locales.each do |target_locale|
167
+ I18n.locale = target_locale
168
+ apply_slug
169
+ end
170
+ ensure
171
+ I18n.locale = orig_locale
172
+ end
173
+ else
174
+ apply_slug
175
+ end
176
+ true
177
+ end
178
+
179
+ def apply_slug
180
+ new_slug = find_unique_slug
181
+
182
+ # skip slug generation and use Mongoid id
183
+ # to find document instead
184
+ return true if new_slug.size.zero?
185
+
186
+ # avoid duplicate slugs
187
+ _slugs.delete(new_slug) if _slugs
188
+
189
+ if !!slug_history && _slugs.is_a?(Array)
190
+ append_slug(new_slug)
191
+ else
192
+ self._slugs = [new_slug]
193
+ end
194
+ end
195
+
196
+ # Builds slug then atomically sets it in the database.
197
+ #
198
+ # This method is adapted to use the :set method variants from both
199
+ # Mongoid 3 (two args) and Mongoid 4 (hash arg)
200
+ def set_slug!
201
+ build_slug
202
+ method(:set).arity == 1 ? set(_slugs: _slugs) : set(:_slugs, _slugs)
203
+ end
204
+
205
+ # Atomically unsets the slug field in the database. It is important to unset
206
+ # the field for the sparse index on slugs.
207
+ #
208
+ # This also resets the in-memory value of the slug field to its default (empty array)
209
+ def unset_slug!
210
+ unset(:_slugs)
211
+ clear_slug!
212
+ end
213
+
214
+ # Rolls back the slug value from the Mongoid changeset.
215
+ def reset_slug!
216
+ reset__slugs!
217
+ end
218
+
219
+ # Sets the slug to its default value.
220
+ def clear_slug!
221
+ self._slugs = []
222
+ end
223
+
224
+ # Finds a unique slug, were specified string used to generate a slug.
225
+ #
226
+ # Returned slug will the same as the specified string when there are no
227
+ # duplicates.
228
+ #
229
+ # @return [String] A unique slug
230
+ def find_unique_slug
231
+ UniqueSlug.new(self).find_unique
232
+ end
233
+
234
+ # @return [Boolean] Whether the slug requires to be rebuilt
235
+ def slug_should_be_rebuilt?
236
+ new_record? || _slugs_changed? || slugged_attributes_changed?
237
+ end
238
+
239
+ def slugged_attributes_changed?
240
+ slugged_attributes.any? { |f| attribute_changed? f.to_s }
241
+ end
242
+
243
+ # @return [String] A string which Action Pack uses for constructing an URL
244
+ # to this record.
245
+ def to_param
246
+ slug || super
247
+ end
248
+
249
+ # @return [String] the slug, or nil if the document does not have a slug.
250
+ def slug
251
+ return _slugs.last if _slugs
252
+ _id.to_s
253
+ end
254
+
255
+ def slug_builder
256
+ cur_slug = nil
257
+ if new_with_slugs? || persisted_with_slug_changes?
258
+ # user defined slug
259
+ cur_slug = _slugs.last
260
+ end
261
+ # generate slug if the slug is not user defined or does not exist
262
+ cur_slug || pre_slug_string
263
+ end
264
+
265
+ private
266
+
267
+ def append_slug(value)
268
+ if localized?
269
+ # This is necessary for the scenario in which the slugged locale is not yet present
270
+ # but the default locale is. In this situation, self._slugs falls back to the default
271
+ # which is undesired
272
+ current_slugs = _slugs_translations.fetch(I18n.locale.to_s, [])
273
+ current_slugs << value
274
+ self._slugs_translations = _slugs_translations.merge(I18n.locale.to_s => current_slugs)
275
+ else
276
+ _slugs << value
277
+ end
278
+ end
279
+
280
+ # Returns true if object is a new record and slugs are present
281
+ def new_with_slugs?
282
+ if localized?
283
+ # We need to check if slugs are present for the locale without falling back
284
+ # to a default
285
+ new_record? && _slugs_translations.fetch(I18n.locale.to_s, []).any?
286
+ else
287
+ new_record? && _slugs.present?
288
+ end
289
+ end
290
+
291
+ # Returns true if object has been persisted and has changes in the slug
292
+ def persisted_with_slug_changes?
293
+ if localized?
294
+ changes = _slugs_change
295
+ return (persisted? && false) if changes.nil?
296
+
297
+ # ensure we check for changes only between the same locale
298
+ original = changes.first.try(:fetch, I18n.locale.to_s, nil)
299
+ compare = changes.last.try(:fetch, I18n.locale.to_s, nil)
300
+ persisted? && original != compare
301
+ else
302
+ persisted? && _slugs_changed?
303
+ end
304
+ end
305
+
306
+ def localized?
307
+ fields['_slugs'].options[:localize]
308
+ rescue StandardError
309
+ false
310
+ end
311
+
312
+ # Return all possible locales for model
313
+ # Avoiding usage of I18n.available_locales in case the user hasn't set it properly, or is
314
+ # doing something crazy, but at the same time we need a fallback in case the model doesn't
315
+ # have any localized attributes at all (extreme edge case).
316
+ def all_locales
317
+ locales = slugged_attributes
318
+ .map { |attr| send("#{attr}_translations").keys if respond_to?("#{attr}_translations") }
319
+ .flatten.compact.uniq
320
+ locales = I18n.available_locales if locales.empty?
321
+ locales
322
+ end
323
+
324
+ def pre_slug_string
325
+ slugged_attributes.map { |f| send f }.join ' '
326
+ end
327
+ end
328
+ end