mongoid_slug 1.0.1 → 2.0.0.pre

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