mongoid-slug 6.0.0 → 6.0.1

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 (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