mongoid_slug 1.0.1 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -43,22 +43,35 @@ book = Book.find params[:book_id]
43
43
 
44
44
  Mongoid Slug will attempt to determine whether you want to find using the `slugs` field or the `_id` field by inspecting the supplied parameters.
45
45
 
46
- * If your document uses `BSON::ObjectId` identifiers, and all arguments passed to `find` are `String` and look like valid `BSON::ObjectId`, then Mongoid Slug will perform a find based on `_id`.
46
+ * Mongoid Slug will perform a find based on `slugs` only if all arguments passed to `find` are of the type `String`
47
+ * If your document uses `BSON::ObjectId` identifiers, and all arguments look like valid `BSON::ObjectId`, then Mongoid Slug will perform a find based on `_id`.
47
48
  * If your document uses any other type of identifiers, and all arguments passed to `find` are of the same type, then Mongoid Slug will perform a find based on `_id`.
48
- * Otherwise, if all arguments passed to `find` are of the type `String`, then Mongoid Slug will perform a find based on `slugs`.
49
+ * If your document uses `String` identifiers and you want to be able find by slugs or ids, to get the correct behaviour, you should add a slug_id_strategy option to your _id field definition. This option should return something that responds to `call` (a callable) and takes one string argument, e.g. a lambda. This callable must return true if the string looks like one of your ids.
49
50
 
50
- To override this behaviour you may supply a hash of options as the final argument to `find` with the key `force_slugs` set to `true` or `false` as required. For example:
51
51
 
52
52
  ```ruby
53
53
  Book.fields['_id'].type
54
54
  => String
55
- book = Book.find 'a-thousand-plateaus' # Finds by _id
55
+ book = Book.find 'a-thousand-plateaus' # Finds by slugs
56
56
  => ...
57
- book = Book.find 'a-thousand-plateaus', { force_slugs: true } # Finds by slugs
58
- => ...
59
- ```
60
57
 
58
+ class Post
59
+ include Mongoid::Document
60
+ include Mongoid::Slug
61
+
62
+ field :_id, type: String, slug_id_strategy: lambda {|id| id.start_with?('....')}
61
63
 
64
+ field :name
65
+ slug :name, :history => true
66
+ end
67
+
68
+ Post.fields['_id'].type
69
+ => String
70
+ post = Post.find 'a-thousand-plateaus' # Finds by slugs
71
+ => ...
72
+ post = Post.find '....1000/Plateus' # Finds by ids
73
+ => ...
74
+ ```
62
75
  [Read here] [4] for all available options.
63
76
 
64
77
  Custom Slug Generation
@@ -171,6 +184,20 @@ Friend.find('admin') # => nil
171
184
  friend.slug # => 'admin-1'
172
185
  ```
173
186
 
187
+ Adhoc checking whether a string is unique on a per Model basis
188
+ --------------------------------------------------------------
189
+
190
+ Lets say you want to have a auto-suggest function on your GUI that could provide a preview of what the url or slug could be before the form to create the record was submitted.
191
+
192
+ You can use the UniqueSlug class in your server side code to do this, e.g.
193
+
194
+ ```ruby
195
+ title = params[:title]
196
+ unique = Mongoid::Slug::UniqueSlug.new(Book.new).find_unique(title)
197
+ ...
198
+ # return some representation of unique
199
+ ```
200
+
174
201
  [1]: https://github.com/rsl/stringex/
175
202
  [2]: https://secure.travis-ci.org/hakanensari/mongoid-slug.png
176
203
  [3]: http://travis-ci.org/hakanensari/mongoid-slug
data/lib/mongoid/slug.rb CHANGED
@@ -15,8 +15,6 @@ module Mongoid
15
15
  end
16
16
 
17
17
  module ClassMethods
18
-
19
-
20
18
  # @overload slug(*fields)
21
19
  # Sets one ore more fields as source of slug.
22
20
  # @param [Array] fields One or more fields the slug should be based on.
@@ -51,10 +49,10 @@ module Mongoid
51
49
  def slug(*fields, &block)
52
50
  options = fields.extract_options!
53
51
 
54
- self.slug_scope = options[:scope]
55
- self.reserved_words = options[:reserve] || Set.new([:new, :edit])
56
- self.slugged_attributes = fields.map &:to_s
57
- self.history = options[:history]
52
+ self.slug_scope = options[:scope]
53
+ self.reserved_words = options[:reserve] || Set.new([:new, :edit])
54
+ self.slugged_attributes = fields.map &:to_s
55
+ self.history = options[:history]
58
56
 
59
57
  unless embedded?
60
58
  if slug_scope
@@ -79,16 +77,12 @@ module Mongoid
79
77
  else
80
78
  set_callback :save, :before, :build_slug, :if => :slug_should_be_rebuilt?
81
79
  end
82
-
83
-
84
80
  end
85
81
 
86
82
  def look_like_slugs?(*args)
87
83
  with_default_scope.look_like_slugs?(*args)
88
84
  end
89
85
 
90
-
91
-
92
86
  # Find documents by slugs.
93
87
  #
94
88
  # A document matches if any of its slugs match one of the supplied params.
@@ -122,7 +116,7 @@ module Mongoid
122
116
  def build_slug
123
117
  _new_slug = find_unique_slug
124
118
  self._slugs.delete(_new_slug)
125
- if self.history == true
119
+ if !!self.history
126
120
  self._slugs << _new_slug
127
121
  else
128
122
  self._slugs = [_new_slug]
@@ -130,89 +124,16 @@ module Mongoid
130
124
  true
131
125
  end
132
126
 
133
-
134
127
  # Finds a unique slug, were specified string used to generate a slug.
135
128
  #
136
129
  # Returned slug will the same as the specified string when there are no
137
130
  # duplicates.
138
131
  #
139
- # @param [String] Desired slug
140
132
  # @return [String] A unique slug
141
133
  def find_unique_slug
142
-
143
- _slug = self.url_builder.call(self)
144
-
145
- # Regular expression that matches slug, slug-1, ... slug-n
146
- # If slug_name field was indexed, MongoDB will utilize that
147
- # index to match /^.../ pattern.
148
- pattern = /^#{Regexp.escape(_slug)}(?:-(\d+))?$/
149
-
150
- where_hash = {}
151
- where_hash[:_slugs.all] = [pattern]
152
- where_hash[:_id.ne] = self._id
153
-
154
- if slug_scope && self.reflect_on_association(slug_scope).nil?
155
- # scope is not an association, so it's scoped to a local field
156
- # (e.g. an association id in a denormalized db design)
157
- where_hash[slug_scope] = self.try(:read_attribute, slug_scope)
158
-
159
- end
160
-
161
- history_slugged_documents =
162
- uniqueness_scope.
163
- where(where_hash)
164
-
165
- existing_slugs = []
166
- existing_history_slugs = []
167
- last_entered_slug = []
168
- history_slugged_documents.each do |doc|
169
- history_slugs = doc._slugs
170
- next if history_slugs.nil?
171
- existing_slugs.push(*history_slugs.find_all { |cur_slug| cur_slug =~ pattern })
172
- last_entered_slug.push(*history_slugs.last) if history_slugs.last =~ pattern
173
- existing_history_slugs.push(*history_slugs.first(history_slugs.length() -1).find_all { |cur_slug| cur_slug =~ pattern })
174
- end
175
-
176
- #do not allow a slug that can be interpreted as the current document id
177
- existing_slugs << _slug unless self.class.look_like_slugs?([_slug])
178
-
179
- #make sure that the slug is not equal to a reserved word
180
- if reserved_words.any? { |word| word === _slug }
181
- existing_slugs << _slug
182
- end
183
-
184
- #only look for a new unique slug if the existing slugs contains the current slug
185
- # - e.g if the slug 'foo-2' is taken, but 'foo' is available, the user can use 'foo'.
186
- if existing_slugs.include? _slug
187
- # If the only conflict is in the history of a document in the same scope,
188
- # transfer the slug
189
- if slug_scope && last_entered_slug.count == 0 && existing_history_slugs.count > 0
190
- history_slugged_documents.each do |doc|
191
- doc._slugs -= existing_history_slugs
192
- doc.save
193
- end
194
- existing_slugs = []
195
- end
196
-
197
- if existing_slugs.count > 0
198
- # Sort the existing_slugs in increasing order by comparing the
199
- # suffix numbers:
200
- # slug, slug-1, slug-2, ..., slug-n
201
- existing_slugs.sort! do |a, b|
202
- (pattern.match(a)[1] || -1).to_i <=>
203
- (pattern.match(b)[1] || -1).to_i
204
- end
205
- max = existing_slugs.last.match(/-(\d+)$/).try(:[], 1).to_i
206
-
207
- _slug += "-#{max + 1}"
208
- end
209
-
210
- end
211
-
212
- _slug
134
+ UniqueSlug.new(self).find_unique
213
135
  end
214
136
 
215
-
216
137
  # @return [Boolean] Whether the slug requires to be rebuilt
217
138
  def slug_should_be_rebuilt?
218
139
  new_record? or _slugs_changed? or slugged_attributes_changed?
@@ -225,65 +146,29 @@ module Mongoid
225
146
  # @return [String] A string which Action Pack uses for constructing an URL
226
147
  # to this record.
227
148
  def to_param
228
- unless _slugs.last
229
- build_slug
230
- save
231
- end
149
+ slug || super
150
+ end
232
151
 
152
+ # @return [String] the slug, or nil if the document does not have a slug.
153
+ def slug
233
154
  _slugs.last
234
155
  end
235
- alias_method :slug, :to_param
236
156
 
237
157
  def slug_builder
238
-
239
158
  _cur_slug = nil
240
159
  if (new_record? and _slugs.present?) or (persisted? and _slugs_changed?)
241
160
  #user defined slug
242
161
  _cur_slug = _slugs.last
243
162
  end
244
-
245
163
  #generate slug if the slug is not user defined or does not exist
246
- unless _cur_slug
247
- self.slugged_attributes.map { |f| self.send f }.join ' '
248
- else
249
- _cur_slug
250
- end
164
+ _cur_slug || pre_slug_string
251
165
  end
252
166
 
253
167
  private
254
168
 
255
- def uniqueness_scope
256
-
257
- if slug_scope &&
258
- metadata = self.reflect_on_association(slug_scope)
259
-
260
- parent = self.send(metadata.name)
261
-
262
- # Make sure doc is actually associated with something, and that
263
- # some referenced docs have been persisted to the parent
264
- #
265
- # TODO: we need better reflection for reference associations,
266
- # like association_name instead of forcing collection_name here
267
- # -- maybe in the forthcoming Mongoid refactorings?
268
- inverse = metadata.inverse_of || collection_name
269
- return parent.respond_to?(inverse) ? parent.send(inverse) : self.class
270
-
271
- end
272
-
273
- if self.embedded?
274
- parent_metadata = reflect_on_all_associations(:embedded_in)[0]
275
- return self._parent.send(parent_metadata.inverse_of || self.metadata.name)
276
- end
277
-
278
- #unless embedded or slug scope, return the deepest document superclass
279
- appropriate_class = self.class
280
- while appropriate_class.superclass.include?(Mongoid::Document)
281
- appropriate_class = appropriate_class.superclass
282
- end
283
- appropriate_class
284
-
169
+ def pre_slug_string
170
+ self.slugged_attributes.map { |f| self.send f }.join ' '
285
171
  end
286
-
287
172
  end
288
173
  end
289
174
 
@@ -50,24 +50,34 @@ module Mongoid
50
50
  for_slugs(slugs).execute_or_raise_for_slugs(slugs, args.multi_arged?)
51
51
  end
52
52
 
53
-
54
- # True if all supplied args look like slugs. Will only attempt to type cast for Moped::BSON::ObjectId.
55
- # Thus '123' will be interpreted as a slug even if the _id is an Integer field, etc.
56
53
  def look_like_slugs?(args)
57
- if args.all? { |id| id.is_a?(String) }
58
- id_type = @klass.fields['_id'].type
59
- case
60
- when id_type == Moped::BSON::ObjectId
61
- args.any? { |id| !Moped::BSON::ObjectId.legal?(id) }
62
- else args.any? { |id| id.class != id_type }
63
- end
64
- else
65
- false
66
- end
54
+ return false unless args.all? { |id| id.is_a?(String) }
55
+ id_field = @klass.fields['_id']
56
+ @slug_strategy ||= id_field.options[:slug_id_strategy] || build_slug_strategy(id_field.type)
57
+ args.none? { |id| @slug_strategy.call(id) }
67
58
  end
68
-
59
+
69
60
  protected
70
61
 
62
+ # unless a :slug_id_strategy option is defined on the id field,
63
+ # use object_id or string strategy depending on the id_type
64
+ # otherwise default for all other id_types
65
+ def build_slug_strategy id_type
66
+ type_method = id_type.to_s.downcase.split('::').last + "_slug_strategy"
67
+ self.respond_to?(type_method) ? method(type_method) : lambda {|id| false}
68
+ end
69
+
70
+ # a string will not look like a slug if it looks like a legal ObjectId
71
+ def objectid_slug_strategy id
72
+ Moped::BSON::ObjectId.legal?(id)
73
+ end
74
+
75
+ # a string will always look like a slug
76
+ def string_slug_strategy id
77
+ true
78
+ end
79
+
80
+
71
81
  def for_slugs(slugs)
72
82
  where({ _slugs: { '$in' => slugs } }).limit(slugs.length)
73
83
  end
@@ -87,4 +97,4 @@ module Mongoid
87
97
  end
88
98
  end
89
99
  end
90
- end
100
+ end
@@ -0,0 +1,3 @@
1
+ Mongoid::Fields.option(:slug_id_strategy) do |model, field, value|
2
+ field.options[:slug_id_strategy] = value
3
+ end
@@ -0,0 +1,143 @@
1
+ require 'forwardable'
2
+
3
+ # Can use e.g. Mongoid::Slug::UniqueSlug.new(ModelClass.new).find_unique "slug-1" for auto-suggest ui
4
+ module Mongoid
5
+ module Slug
6
+ class UniqueSlug
7
+
8
+ class SlugState
9
+ attr_reader :last_entered_slug, :existing_slugs, :existing_history_slugs, :sorted_existing
10
+
11
+ def initialize slug, documents, pattern
12
+ @slug = slug
13
+ @documents = documents
14
+ @pattern = pattern
15
+ @last_entered_slug = []
16
+ @existing_slugs = []
17
+ @existing_history_slugs = []
18
+ @sorted_existing = []
19
+ @documents.each do |doc|
20
+ history_slugs = doc._slugs
21
+ next if history_slugs.nil?
22
+ existing_slugs.push(*history_slugs.find_all { |cur_slug| cur_slug =~ @pattern })
23
+ last_entered_slug.push(*history_slugs.last) if history_slugs.last =~ @pattern
24
+ existing_history_slugs.push(*history_slugs.first(history_slugs.length() - 1).find_all { |cur_slug| cur_slug =~ @pattern })
25
+ end
26
+ end
27
+
28
+ def slug_included?
29
+ existing_slugs.include? @slug
30
+ end
31
+
32
+ def include_slug
33
+ existing_slugs << @slug
34
+ end
35
+
36
+ def highest_existing_counter
37
+ sort_existing_slugs
38
+ @sorted_existing.last || 0
39
+ end
40
+
41
+ def sort_existing_slugs
42
+ # remove the slug part and leave the absolute integer part and sort
43
+ re = %r(^#{Regexp.escape(@slug)})
44
+ @sorted_existing = existing_slugs.map do |s|
45
+ s.sub(re,'').to_i.abs
46
+ end.sort
47
+ end
48
+
49
+ def inspect
50
+ {
51
+ :slug => @slug,
52
+ :existing_slugs => existing_slugs,
53
+ :last_entered_slug => last_entered_slug,
54
+ :existing_history_slugs => existing_history_slugs,
55
+ :sorted_existing => sorted_existing
56
+ }
57
+ end
58
+ end
59
+
60
+ extend Forwardable
61
+
62
+ attr_reader :model, :_slug
63
+
64
+ def_delegators :@model, :slug_scope, :reflect_on_association, :read_attribute,
65
+ :check_against_id, :reserved_words, :url_builder, :metadata,
66
+ :collection_name, :embedded?, :reflect_on_all_associations
67
+
68
+ def initialize model
69
+ @model = model
70
+ @_slug = ""
71
+ @state = nil
72
+ end
73
+
74
+ def find_unique attempt = nil
75
+ @_slug = if attempt
76
+ attempt.to_url
77
+ else
78
+ url_builder.call(model)
79
+ end
80
+ # Regular expression that matches slug, slug-1, ... slug-n
81
+ # If slug_name field was indexed, MongoDB will utilize that
82
+ # index to match /^.../ pattern.
83
+ pattern = /^#{Regexp.escape(_slug)}(?:-(\d+))?$/
84
+
85
+ where_hash = {}
86
+ where_hash[:_slugs.all] = [pattern]
87
+ where_hash[:_id.ne] = model._id
88
+
89
+ if (scope = slug_scope) && reflect_on_association(scope).nil?
90
+ # scope is not an association, so it's scoped to a local field
91
+ # (e.g. an association id in a denormalized db design)
92
+ where_hash[scope] = model.try(:read_attribute, scope)
93
+ end
94
+
95
+ @state = SlugState.new _slug, uniqueness_scope.where(where_hash), pattern
96
+
97
+ # do not allow a slug that can be interpreted as the current document id
98
+ @state.include_slug unless model.class.look_like_slugs?([_slug])
99
+
100
+ # make sure that the slug is not equal to a reserved word
101
+ @state.include_slug if reserved_words.any? { |word| word === _slug }
102
+
103
+ # only look for a new unique slug if the existing slugs contains the current slug
104
+ # - e.g if the slug 'foo-2' is taken, but 'foo' is available, the user can use 'foo'.
105
+ if @state.slug_included?
106
+ highest = @state.highest_existing_counter
107
+ @_slug += "-#{highest.succ}"
108
+ end
109
+ _slug
110
+ end
111
+
112
+ def uniqueness_scope
113
+
114
+ if slug_scope &&
115
+ metadata = reflect_on_association(slug_scope)
116
+
117
+ parent = model.send(metadata.name)
118
+
119
+ # Make sure doc is actually associated with something, and that
120
+ # some referenced docs have been persisted to the parent
121
+ #
122
+ # TODO: we need better reflection for reference associations,
123
+ # like association_name instead of forcing collection_name here
124
+ # -- maybe in the forthcoming Mongoid refactorings?
125
+ inverse = metadata.inverse_of || collection_name
126
+ return parent.respond_to?(inverse) ? parent.send(inverse) : model.class
127
+ end
128
+
129
+ if embedded?
130
+ parent_metadata = reflect_on_all_associations(:embedded_in)[0]
131
+ return model._parent.send(parent_metadata.inverse_of || model.metadata.name)
132
+ end
133
+
134
+ #unless embedded or slug scope, return the deepest document superclass
135
+ appropriate_class = model.class
136
+ while appropriate_class.superclass.include?(Mongoid::Document)
137
+ appropriate_class = appropriate_class.superclass
138
+ end
139
+ appropriate_class
140
+ end
141
+ end
142
+ end
143
+ end
@@ -1,5 +1,5 @@
1
1
  module Mongoid #:nodoc:
2
2
  module Slug
3
- VERSION = '1.0.1'
3
+ VERSION = '2.0.0.pre'
4
4
  end
5
5
  end
data/lib/mongoid_slug.rb CHANGED
@@ -2,3 +2,5 @@ require 'mongoid'
2
2
  require 'stringex'
3
3
  require 'mongoid/slug'
4
4
  require 'mongoid/slug/criteria'
5
+ require 'mongoid/slug/unique_slug'
6
+ require 'mongoid/slug/slug_id_strategy'
@@ -0,0 +1,12 @@
1
+ #encoding: utf-8
2
+ class Entity
3
+ include Mongoid::Document
4
+ include Mongoid::Slug
5
+
6
+ field :_id, type: String, slug_id_strategy: UuidIdStrategy
7
+
8
+ field :name
9
+ field :user_edited_variation
10
+
11
+ slug :user_edited_variation, :history => true
12
+ end
@@ -7,7 +7,58 @@ module Mongoid
7
7
  Book.create(:title => "A Thousand Plateaus")
8
8
  end
9
9
 
10
+ context "when option skip_id_check is used with UUID _id " do
11
+ let(:entity0) do
12
+ Entity.create(:_id => UUID.generate, :name => 'Pelham 1 2 3', :user_edited_variation => 'pelham-1-2-3')
13
+ end
14
+ let(:entity1) do
15
+ Entity.create(:_id => UUID.generate, :name => 'Jackson 5', :user_edited_variation => 'jackson-5')
16
+ end
17
+ let(:entity2) do
18
+ Entity.create(:_id => UUID.generate, :name => 'Jackson 5', :user_edited_variation => 'jackson-5')
19
+ end
20
+
21
+ it "generates a unique slug by appending a counter to duplicate text" do
22
+ entity0.to_param.should eql "pelham-1-2-3"
23
+
24
+ 5.times{ |x|
25
+ dup = Entity.create(:_id => UUID.generate, :name => entity0.name, :user_edited_variation => entity0.user_edited_variation)
26
+ dup.to_param.should eql "pelham-1-2-3-#{x.succ}"
27
+ }
28
+ end
29
+
30
+ it "allows the user to edit the sluggable field" do
31
+ entity1.to_param.should eql "jackson-5"
32
+ entity2.to_param.should eql "jackson-5-1"
33
+ entity2.user_edited_variation = "jackson-5-indiana"
34
+ entity2.save
35
+ entity2.to_param.should eql "jackson-5-indiana"
36
+ end
37
+
38
+ it "allows users to edit the sluggable field" do
39
+ entity1.to_param.should eql "jackson-5"
40
+ entity2.to_param.should eql "jackson-5-1"
41
+ entity2.user_edited_variation = "jackson-5-indiana"
42
+ entity2.save
43
+ entity2.to_param.should eql "jackson-5-indiana"
44
+ end
45
+
46
+ it "it restores the slug if the editing user tries to use an existing slug" do
47
+ entity1.to_param.should eql "jackson-5"
48
+ entity2.to_param.should eql "jackson-5-1"
49
+ entity2.user_edited_variation = "jackson-5"
50
+ entity2.save
51
+ entity2.to_param.should eql "jackson-5-1"
52
+ end
53
+
54
+ it "does not force an appended counter on a plain string" do
55
+ entity = Entity.create(:_id => UUID.generate, :name => 'Adele', :user_edited_variation => 'adele')
56
+ entity.to_param.should eql "adele"
57
+ end
58
+ end
59
+
10
60
  context "when the object is top-level" do
61
+
11
62
  it "generates a slug" do
12
63
  book.to_param.should eql "a-thousand-plateaus"
13
64
  end
@@ -26,8 +77,9 @@ module Mongoid
26
77
  end
27
78
 
28
79
  it "does not allow a Moped::BSON::ObjectId as use for a slug" do
29
- bad = Book.create(:title => "4ea0389f0364313d79104fb3")
30
- bad.slugs.should_not include("4ea0389f0364313d79104fb3")
80
+ bson_id = Moped::BSON::ObjectId.new.to_s
81
+ bad = Book.create(:title => bson_id)
82
+ bad.slugs.should_not include(bson_id)
31
83
  end
32
84
 
33
85
  it "does not update slug if slugged fields have not changed" do
@@ -352,30 +404,6 @@ module Mongoid
352
404
  dup.to_param.should eql character.to_param
353
405
  end
354
406
  end
355
-
356
- context "when using history and reusing a slug within the scope" do
357
- let!(:subject1) do
358
- book.subjects.create(:name => "A Subject")
359
- end
360
- let!(:subject2) do
361
- book.subjects.create(:name => "Another Subject")
362
- end
363
-
364
- before(:each) do
365
- subject1.name = "Something Else Entirely"
366
- subject1.save
367
- subject2.name = "A Subject"
368
- subject2.save
369
- end
370
-
371
- it "allows using the slug" do
372
- subject2.slugs.should include("a-subject")
373
- end
374
-
375
- it "removes the slug from the old owner's history" do
376
- subject1.slugs.should_not include("a-subject")
377
- end
378
- end
379
407
  end
380
408
 
381
409
  context "when slug is scoped by one of the class's own fields" do
@@ -530,7 +558,7 @@ module Mongoid
530
558
  let!(:subject2) { Subject.create(:title => "A Subject", :book => book2) }
531
559
  let!(:without_slug) { WithoutSlug.new().tap { |d| d.id = 456; d.save } }
532
560
 
533
- context "when the model does not use mongoid slugs" do
561
+ context "when the model does not use mongoid slugs" do
534
562
  it "should not use mongoid slug's custom find methods" do
535
563
  Mongoid::Slug::Criteria.any_instance.should_not_receive(:find)
536
564
  WithoutSlug.find(without_slug.id.to_s).should == without_slug
@@ -704,15 +732,28 @@ module Mongoid
704
732
  end
705
733
 
706
734
  describe "#to_param" do
735
+ context "when called on a new record" do
736
+ let(:book) { Book.new }
737
+
738
+ it "should return nil" do
739
+ book.to_param.should be_nil
740
+ end
741
+
742
+ it "should not persist the record" do
743
+ book.to_param
744
+ book.should_not be_persisted
745
+ end
746
+ end
747
+
707
748
  context "when called on an existing record with no slug" do
708
749
  before do
709
750
  Book.collection.insert(:title => "Proust and Signs")
710
751
  end
711
752
 
712
- it "generates the missing slug" do
753
+ it "should return the id" do
713
754
  book = Book.first
714
- book.to_param
715
- book.reload.slugs.should include("proust-and-signs")
755
+ book.to_param.should == book.id.to_s
756
+ book.reload.slugs.should be_empty
716
757
  end
717
758
  end
718
759
  end
@@ -767,6 +808,21 @@ module Mongoid
767
808
  book.save
768
809
  book.to_param.should eql "not-it-either"
769
810
  end
811
+
812
+ it "updates the slug when a new one is appended" do
813
+ book = Book.create(:title => "A Thousand Plateaus", :slugs => ["not-what-you-expected"])
814
+ book.slugs.push "not-it-either"
815
+ book.save
816
+ book.to_param.should eql "not-it-either"
817
+ end
818
+
819
+ it "updates the slug to a unique slug when a new one is appended" do
820
+ book1 = Book.create(:title => "Sleepyhead")
821
+ book2 = Book.create(:title => "A Thousand Plateaus")
822
+ book2.slugs.push "sleepyhead"
823
+ book2.save
824
+ book2.to_param.should eql "sleepyhead-1"
825
+ end
770
826
  end
771
827
 
772
828
  context "when it is set to an empty string" do
data/spec/spec_helper.rb CHANGED
@@ -3,9 +3,17 @@ begin
3
3
  rescue LoadError
4
4
  end
5
5
  require 'rspec'
6
+ require 'uuid'
7
+ require "awesome_print"
6
8
 
7
9
  require File.expand_path '../../lib/mongoid_slug', __FILE__
8
10
 
11
+ module Mongoid::Slug::UuidIdStrategy
12
+ def self.call id
13
+ id =~ /\A([0-9a-fA-F]){8}-(([0-9a-fA-F]){4}-){3}([0-9a-fA-F]){12}\z/
14
+ end
15
+ end
16
+
9
17
  def database_id
10
18
  ENV['CI'] ? "mongoid_slug_#{Process.pid}" : 'mongoid_slug_test'
11
19
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid_slug
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
5
- prerelease:
4
+ version: 2.0.0.pre
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andreas Saebjoernsen
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-16 00:00:00.000000000 Z
12
+ date: 2012-10-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongoid
@@ -91,6 +91,38 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: awesome_print
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: uuid
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
94
126
  description: Mongoid URL slug or permalink generator
95
127
  email:
96
128
  - andy@cosemble.com
@@ -99,6 +131,8 @@ extensions: []
99
131
  extra_rdoc_files: []
100
132
  files:
101
133
  - lib/mongoid/slug/criteria.rb
134
+ - lib/mongoid/slug/slug_id_strategy.rb
135
+ - lib/mongoid/slug/unique_slug.rb
102
136
  - lib/mongoid/slug/version.rb
103
137
  - lib/mongoid/slug.rb
104
138
  - lib/mongoid_slug.rb
@@ -109,6 +143,7 @@ files:
109
143
  - spec/models/author.rb
110
144
  - spec/models/book.rb
111
145
  - spec/models/caption.rb
146
+ - spec/models/entity.rb
112
147
  - spec/models/friend.rb
113
148
  - spec/models/integer_id.rb
114
149
  - spec/models/magazine.rb
@@ -121,7 +156,7 @@ files:
121
156
  - spec/models/without_slug.rb
122
157
  - spec/mongoid/slug_spec.rb
123
158
  - spec/spec_helper.rb
124
- homepage: http://github.com/digitalplaywright/mongoid-slug
159
+ homepage: http://github.com/musicglue/mongoid-slug
125
160
  licenses: []
126
161
  post_install_message:
127
162
  rdoc_options: []
@@ -136,9 +171,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
171
  required_rubygems_version: !ruby/object:Gem::Requirement
137
172
  none: false
138
173
  requirements:
139
- - - ! '>='
174
+ - - ! '>'
140
175
  - !ruby/object:Gem::Version
141
- version: '0'
176
+ version: 1.3.1
142
177
  requirements: []
143
178
  rubyforge_project: mongoid_slug
144
179
  rubygems_version: 1.8.24
@@ -151,6 +186,7 @@ test_files:
151
186
  - spec/models/author.rb
152
187
  - spec/models/book.rb
153
188
  - spec/models/caption.rb
189
+ - spec/models/entity.rb
154
190
  - spec/models/friend.rb
155
191
  - spec/models/integer_id.rb
156
192
  - spec/models/magazine.rb