mongoid_localized_slug 0.1.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.
- data/LICENSE +20 -0
- data/README.md +73 -0
- data/lib/mongoid/localized_slug.rb +208 -0
- data/lib/mongoid/localized_slug/criterion.rb +25 -0
- data/lib/mongoid/localized_slug/version.rb +5 -0
- data/lib/mongoid_localized_slug.rb +4 -0
- data/spec/models/animal.rb +8 -0
- data/spec/models/book.rb +6 -0
- data/spec/models/friend.rb +6 -0
- data/spec/models/person.rb +6 -0
- data/spec/mongoid/slug_spec.rb +336 -0
- data/spec/spec_helper.rb +20 -0
- metadata +135 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-2012 Hakan Ensari
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Mongoid LocalizedSlug
|
2
|
+
============
|
3
|
+
|
4
|
+
WARNING: This is a simplified version of the
|
5
|
+
[mongoid-slug gem](https://github.com/hakanensari/mongoid-slug) that supports
|
6
|
+
localized fields. It DOES NOT support nor embedded objects neither slug history.
|
7
|
+
If you need any of those features stick with the original gem.
|
8
|
+
|
9
|
+
Mongoid LocalizedSlug generates a URL slug or permalink based on one localized
|
10
|
+
fields in a Mongoid model. It sits idly on top of [stringex] [1], supporting
|
11
|
+
non-Latin characters.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
------------
|
15
|
+
|
16
|
+
Add to your Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'mongoid_localized_slug'
|
20
|
+
```
|
21
|
+
|
22
|
+
Usage
|
23
|
+
-----
|
24
|
+
|
25
|
+
Set up a slug:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class Book
|
29
|
+
include Mongoid::Document
|
30
|
+
include Mongoid::LocalizedSlug
|
31
|
+
|
32
|
+
field :title, localize: true
|
33
|
+
slug :title, index: true
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Create a record with multiple translations:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
I18n.locale = :en
|
41
|
+
book = Book.create(:title => "A Thousand Plateaus")
|
42
|
+
I18n.locale = :es
|
43
|
+
book.update_attribute :title, 'Mil Mesetas'
|
44
|
+
```
|
45
|
+
|
46
|
+
Find a record by its slug:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# GET /books/a-thousand-plateaus
|
50
|
+
book = Book.find_by_slug params[:book_id]
|
51
|
+
# GET /books/mil-mesetas
|
52
|
+
book = Book.find_by_slug params[:book_id] # matches the same Book object
|
53
|
+
```
|
54
|
+
|
55
|
+
Reserved Slugs
|
56
|
+
--------------
|
57
|
+
|
58
|
+
Pass words you do not want to be slugged using the `reserve` option:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class Friend
|
62
|
+
include Mongoid::Document
|
63
|
+
|
64
|
+
field :name
|
65
|
+
slug :name, reserve: ['admin', 'root']
|
66
|
+
end
|
67
|
+
|
68
|
+
friend = Friend.create name: 'admin'
|
69
|
+
Friend.find_by_slug('admin') # => nil
|
70
|
+
friend.slug # => 'admin-1'
|
71
|
+
```
|
72
|
+
|
73
|
+
[1]: https://github.com/rsl/stringex/
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Mongoid
|
2
|
+
# The Slug module helps you generate a URL slug or permalink based on one or
|
3
|
+
# more fields in a Mongoid model.
|
4
|
+
module LocalizedSlug
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
cattr_accessor :slug_name,
|
9
|
+
:reserved_words_in_slug,
|
10
|
+
:slugged_attribute
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# @overload slug(*fields)
|
15
|
+
# Sets one ore more fields as source of slug.
|
16
|
+
# @param [Symbol] The fields the slug should be based on.
|
17
|
+
#
|
18
|
+
# @overload slug(*field, options)
|
19
|
+
# Sets one ore more fields as source of slug.
|
20
|
+
# @param [Symbol] The the slug should be based on.
|
21
|
+
# @param [Hash] options
|
22
|
+
# @param options [String] :as The name of the field that stores the
|
23
|
+
# slug. Defaults to `slug`.
|
24
|
+
# @param options [Boolean] :index Whether an index should be defined
|
25
|
+
# on the slug field. Defaults to `false`.
|
26
|
+
# @param options [Boolean] :permanent Whether the slug should be
|
27
|
+
# immutable. Defaults to `false`.
|
28
|
+
# @param options [Array] :reserve` A list of reserved slugs
|
29
|
+
#
|
30
|
+
# @example A custom builder
|
31
|
+
# class Person
|
32
|
+
# include Mongoid::Document
|
33
|
+
# include Mongoid::LocalizedSlug
|
34
|
+
#
|
35
|
+
# field :name, localize: true
|
36
|
+
#
|
37
|
+
# slug :name, :index => true
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def slug(*field_and_options)
|
41
|
+
options = field_and_options.extract_options!
|
42
|
+
|
43
|
+
self.reserved_words_in_slug = options[:reserve] || []
|
44
|
+
self.slug_name = options[:as] || :slug
|
45
|
+
self.slugged_attribute = field_and_options.first.to_s
|
46
|
+
|
47
|
+
field slug_name, :type => Array, :default => []
|
48
|
+
field "#{slug_name}_translations", :type => Hash, :default => {}
|
49
|
+
|
50
|
+
unless slug_name == :slug
|
51
|
+
alias_attribute :slug, slug_name
|
52
|
+
alias_attribute :slug_translations, "#{slug_name}_translations"
|
53
|
+
end
|
54
|
+
|
55
|
+
if options[:index]
|
56
|
+
index slug_name, :unique => true
|
57
|
+
end
|
58
|
+
|
59
|
+
set_callback options[:permanent] ? :create : :save, :before do |doc|
|
60
|
+
doc.build_slug if doc.slug_should_be_rebuilt?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Build a finder for slug.
|
64
|
+
#
|
65
|
+
# Defaults to `find_by_slug`.
|
66
|
+
instance_eval <<-CODE
|
67
|
+
def self.find_by_#{slug_name}(slug)
|
68
|
+
where(slug_name => slug).first
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.find_by_#{slug_name}!(slug)
|
72
|
+
self.find_by_#{slug_name}(slug) ||
|
73
|
+
raise(Mongoid::Errors::DocumentNotFound.new self, slug)
|
74
|
+
end
|
75
|
+
CODE
|
76
|
+
|
77
|
+
# Build a scope based on the slug name.
|
78
|
+
#
|
79
|
+
# Defaults to `by_slug`.
|
80
|
+
scope "by_#{slug_name}".to_sym, lambda { |slug|
|
81
|
+
where(slug_name => slug)
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Finds a unique slug, were specified string used to generate a slug.
|
86
|
+
#
|
87
|
+
# Returned slug will the same as the specified string when there are no
|
88
|
+
# duplicates.
|
89
|
+
#
|
90
|
+
# @param [String] desired_slug
|
91
|
+
# @param [Hash] options
|
92
|
+
# @param options [Symbol] :scope The scope that should be used to
|
93
|
+
# generate the slug, if the class creates scoped slugs. Defaults to
|
94
|
+
# `nil`.
|
95
|
+
# @param options [Constant] :model The model that the slug should be
|
96
|
+
# generated for. This option overrides `:scope`, as the scope can now
|
97
|
+
# be extracted from the model. Defaults to `nil`.
|
98
|
+
# @return [String] A unique slug
|
99
|
+
def find_unique_slug_for(desired_slug, options = {})
|
100
|
+
excluded_id = options[:model]._id if options[:model]
|
101
|
+
|
102
|
+
slug = desired_slug.to_url
|
103
|
+
|
104
|
+
# Regular expression that matches slug, slug-1, ... slug-n
|
105
|
+
# If slug_name field was indexed, MongoDB will utilize that
|
106
|
+
# index to match /^.../ pattern.
|
107
|
+
pattern = /^#{Regexp.escape(slug)}(?:-(\d+))?$/
|
108
|
+
|
109
|
+
where_hash = {}
|
110
|
+
where_hash[slug_name] = pattern
|
111
|
+
where_hash[:_id.ne] = excluded_id if excluded_id
|
112
|
+
|
113
|
+
existing_slugs =
|
114
|
+
only(slug_name).
|
115
|
+
where(where_hash)
|
116
|
+
|
117
|
+
# p existing_slugs
|
118
|
+
existing_slugs = existing_slugs.map do |doc|
|
119
|
+
doc.slug
|
120
|
+
end.flatten
|
121
|
+
# p existing_slugs
|
122
|
+
|
123
|
+
# Do not allow BSON::ObjectIds as slugs
|
124
|
+
existing_slugs << slug if BSON::ObjectId.legal?(slug)
|
125
|
+
|
126
|
+
if reserved_words_in_slug.any? { |word| word === slug }
|
127
|
+
existing_slugs << slug
|
128
|
+
end
|
129
|
+
|
130
|
+
if existing_slugs.count > 0
|
131
|
+
# Sort the existing_slugs in increasing order by comparing the
|
132
|
+
# suffix numbers:
|
133
|
+
# slug, slug-1, slug-2, ..., slug-n
|
134
|
+
existing_slugs.sort! do |a, b|
|
135
|
+
(pattern.match(a)[1] || -1).to_i <=>
|
136
|
+
(pattern.match(b)[1] || -1).to_i
|
137
|
+
end
|
138
|
+
max = existing_slugs.last.match(/-(\d+)$/).try(:[], 1).to_i
|
139
|
+
|
140
|
+
slug += "-#{max + 1}"
|
141
|
+
end
|
142
|
+
|
143
|
+
slug
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
# Builds a new slug.
|
149
|
+
#
|
150
|
+
# @return [true]
|
151
|
+
def build_slug
|
152
|
+
translations = self.send("#{slugged_attribute}_translations")
|
153
|
+
slugs = []
|
154
|
+
trans_hash = {}
|
155
|
+
translations.each do |lang, text|
|
156
|
+
trans_hash[lang] = find_unique_slug_for(text)
|
157
|
+
slugs << trans_hash[lang]
|
158
|
+
end
|
159
|
+
self.send("#{slug_name}_translations=", trans_hash)
|
160
|
+
self.send("#{slug_name}=", slugs.uniq)
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
# Finds a unique slug, were specified string used to generate a slug.
|
165
|
+
#
|
166
|
+
# Returned slug will the same as the specified string when there are no
|
167
|
+
# duplicates.
|
168
|
+
#
|
169
|
+
# @param [String] Desired slug
|
170
|
+
# @return [String] A unique slug
|
171
|
+
def find_unique_slug_for(desired_slug)
|
172
|
+
self.class.find_unique_slug_for desired_slug, :model => self
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Boolean] Whether the slug requires to be rebuilt
|
176
|
+
def slug_should_be_rebuilt?
|
177
|
+
new_record? or slug_changed? or slugged_attribute_changed?
|
178
|
+
end
|
179
|
+
|
180
|
+
def slugged_attribute_changed?
|
181
|
+
attribute_changed? slugged_attribute
|
182
|
+
end
|
183
|
+
|
184
|
+
# @return [String] A string which Action Pack uses for constructing an URL
|
185
|
+
# to this record.
|
186
|
+
def to_param
|
187
|
+
if slug.empty?
|
188
|
+
build_slug
|
189
|
+
save
|
190
|
+
end
|
191
|
+
locale = I18n.locale.to_s
|
192
|
+
slug_translations.each do |lang, slug|
|
193
|
+
return slug if lang == locale
|
194
|
+
end
|
195
|
+
slug.first
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def find_unique_slug
|
201
|
+
find_unique_slug_for user_defined_slug
|
202
|
+
end
|
203
|
+
|
204
|
+
def user_defined_slug
|
205
|
+
slug if (new_record? and slug.present?) or (persisted? and slug_changed?)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Mongoid::LocalizedSlug::Criterion
|
2
|
+
# Override Mongoid's finder to use slug or id
|
3
|
+
def for_ids(*ids)
|
4
|
+
return super unless @klass.ancestors.include?(Mongoid::LocalizedSlug)
|
5
|
+
|
6
|
+
# We definitely don't want to rescue at the same level we call super above -
|
7
|
+
# that would risk applying our slug behavior to non-slug objects, in the case
|
8
|
+
# where their id conversion fails and super raises BSON::InvalidObjectId
|
9
|
+
begin
|
10
|
+
# note that there is a small possibility that a client could create a slug that
|
11
|
+
# resembles a BSON::ObjectId
|
12
|
+
ids.flatten!
|
13
|
+
BSON::ObjectId.from_string(ids.first) unless ids.first.is_a?(BSON::ObjectId)
|
14
|
+
super # Fallback to original Mongoid::Criterion::Optional
|
15
|
+
rescue BSON::InvalidObjectId
|
16
|
+
# slug
|
17
|
+
if ids.size > 1
|
18
|
+
where(@klass.slug_name.to_sym.in => ids)
|
19
|
+
else
|
20
|
+
where(@klass.slug_name => ids.first)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
Mongoid::Criteria.send :include, Mongoid::LocalizedSlug::Criterion
|
data/spec/models/book.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
module Mongoid
|
5
|
+
describe LocalizedSlug do
|
6
|
+
let(:book) do
|
7
|
+
Book.create(:title => "A Thousand Plateaus")
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when the object is top-level" do
|
11
|
+
it "generates a slug" do
|
12
|
+
book.to_param.should eql "a-thousand-plateaus"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "updates the slug" do
|
16
|
+
book.title = "Anti Oedipus"
|
17
|
+
book.save
|
18
|
+
book.to_param.should eql "anti-oedipus"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "generates a unique slug by appending a counter to duplicate text" do
|
22
|
+
15.times{ |x|
|
23
|
+
dup = Book.create(:title => book.title)
|
24
|
+
dup.to_param.should eql "a-thousand-plateaus-#{x+1}"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "does not allow a BSON::ObjectId as use for a slug" do
|
29
|
+
bad = Book.create(:title => "4ea0389f0364313d79104fb3")
|
30
|
+
bad.to_param.should_not eql "4ea0389f0364313d79104fb3"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "does not update slug if slugged fields have not changed" do
|
34
|
+
book.save
|
35
|
+
book.to_param.should eql "a-thousand-plateaus"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not change slug if slugged fields have changed but generated slug is identical" do
|
39
|
+
book.title = "a thousand plateaus"
|
40
|
+
book.save
|
41
|
+
book.to_param.should eql "a-thousand-plateaus"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "generate diffetent slugs for every traslation" do
|
45
|
+
I18n.locale = :en
|
46
|
+
book.to_param.should eql 'a-thousand-plateaus'
|
47
|
+
I18n.locale = :es
|
48
|
+
book.update_attribute :title, 'Platero y yo'
|
49
|
+
book.to_param.should eql 'platero-y-yo'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "finds by slug" do
|
53
|
+
Book.find_by_slug(book.to_param).should eql book
|
54
|
+
end
|
55
|
+
|
56
|
+
it "find by any translated slug" do
|
57
|
+
I18n.locale = :en
|
58
|
+
book.to_param.should eql 'a-thousand-plateaus'
|
59
|
+
I18n.locale = :es
|
60
|
+
book.update_attribute :title, 'Platero y yo'
|
61
|
+
book.to_param.should eql 'platero-y-yo'
|
62
|
+
end
|
63
|
+
|
64
|
+
context "using find" do
|
65
|
+
it "finds by slug" do
|
66
|
+
Book.find(book.to_param).should eql book
|
67
|
+
end
|
68
|
+
|
69
|
+
it "finds by id as string" do
|
70
|
+
Book.find(book.id.to_s).should eql book
|
71
|
+
end
|
72
|
+
|
73
|
+
it "finds by id as array of strings" do
|
74
|
+
Book.find([book.id.to_s]).should eql [book]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "finds by id as BSON::ObjectId" do
|
78
|
+
Book.find(book.id).should eql book
|
79
|
+
end
|
80
|
+
|
81
|
+
it "finds by id as an array of BSON::ObjectIds" do
|
82
|
+
Book.find([book.id]).should eql [book]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns an empty array if given an empty array" do
|
86
|
+
Book.find([]).should eql []
|
87
|
+
end
|
88
|
+
|
89
|
+
it "find by any translated slug" do
|
90
|
+
book.save
|
91
|
+
Book.find('a-thousand-plateaus').should eql book
|
92
|
+
I18n.locale = :es
|
93
|
+
book.update_attribute :title, 'Platero y yo'
|
94
|
+
Book.find('platero-y-yo').should eql book
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when :as is passed as an argument" do
|
100
|
+
let!(:person) do
|
101
|
+
Person.create(:name => "John Doe")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "finds by slug" do
|
105
|
+
Person.find_by_permalink("john-doe").should eql person
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'defines #slug' do
|
109
|
+
person.should respond_to :slug
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'defines #slug_changed?' do
|
113
|
+
person.should respond_to :slug_changed?
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'defines #slug_was' do
|
117
|
+
person.should respond_to :slug_was
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when :permanent is passed as an argument" do
|
122
|
+
let(:person) do
|
123
|
+
Person.create(:name => "John Doe")
|
124
|
+
end
|
125
|
+
|
126
|
+
it "does not update the slug when the slugged fields change" do
|
127
|
+
person.name = "Jane Doe"
|
128
|
+
person.save
|
129
|
+
person.to_param.should eql "john-doe"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when slugged field contains non-ASCII characters" do
|
134
|
+
it "slugs Cyrillic characters" do
|
135
|
+
book.title = "Капитал"
|
136
|
+
book.save
|
137
|
+
book.to_param.should eql "kapital"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "slugs Greek characters" do
|
141
|
+
book.title = "Ελλάδα"
|
142
|
+
book.save
|
143
|
+
book.to_param.should eql "ellada"
|
144
|
+
end
|
145
|
+
|
146
|
+
it "slugs Chinese characters" do
|
147
|
+
book.title = "中文"
|
148
|
+
book.save
|
149
|
+
book.to_param.should eql "zhong-wen"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "slugs non-ASCII Latin characters" do
|
153
|
+
book.title = "Paul Cézanne"
|
154
|
+
book.save
|
155
|
+
book.to_param.should eql "paul-cezanne"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when :index is passed as an argument" do
|
160
|
+
before do
|
161
|
+
Book.collection.drop_indexes
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when slug is not scoped by a reference association" do
|
165
|
+
it "defines an index on the slug" do
|
166
|
+
Book.create_indexes
|
167
|
+
Book.collection.index_information.should have_key "slug_1"
|
168
|
+
end
|
169
|
+
|
170
|
+
it "defines a unique index" do
|
171
|
+
Book.create_indexes
|
172
|
+
Book.index_information["slug_1"]["unique"].should be_true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when :index is not passed as an argument" do
|
179
|
+
it "does not define an index on the slug" do
|
180
|
+
Person.create_indexes
|
181
|
+
Person.collection.index_information.should_not have_key "permalink_1"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context "when :reserve is passed" do
|
186
|
+
it "does not use the the reserved slugs" do
|
187
|
+
friend1 = Friend.create(:name => "foo")
|
188
|
+
friend1.to_param.should_not eql("foo")
|
189
|
+
friend1.to_param.should eql("foo-1")
|
190
|
+
|
191
|
+
friend2 = Friend.create(:name => "bar")
|
192
|
+
friend2.to_param.should_not eql("bar")
|
193
|
+
friend2.to_param.should eql("bar-1")
|
194
|
+
|
195
|
+
friend3 = Friend.create(:name => "en")
|
196
|
+
friend3.to_param.should_not eql("en")
|
197
|
+
friend3.to_param.should eql("en-1")
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should start with concatenation -1" do
|
201
|
+
friend1 = Friend.create(:name => "foo")
|
202
|
+
friend1.to_param.should eql("foo-1")
|
203
|
+
friend2 = Friend.create(:name => "foo")
|
204
|
+
friend2.to_param.should eql("foo-2")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe ".find_by_slug" do
|
209
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
210
|
+
|
211
|
+
it "returns nil if no document is found" do
|
212
|
+
Book.find_by_slug(:title => "Anti Oedipus").should be_nil
|
213
|
+
end
|
214
|
+
|
215
|
+
it "returns the document if it is found" do
|
216
|
+
Book.find_by_slug(book.to_param).should == book
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe ".find_by_slug!" do
|
221
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
222
|
+
|
223
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
224
|
+
lambda {
|
225
|
+
Book.find_by_slug!(:title => "Anti Oedipus")
|
226
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "returns the document when it is found" do
|
230
|
+
Book.find_by_slug!(book.to_param).should == book
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe ".find" do
|
235
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
236
|
+
let!(:book2) { Book.create(:title => "Difference and Repetition") }
|
237
|
+
let!(:friend) { Friend.create(:name => "Jim Bob") }
|
238
|
+
let!(:friend2) { Friend.create(:name => "Billy Bob") }
|
239
|
+
let!(:animal) { Animal.create(:name => "Cardu", :nickname => "Car") }
|
240
|
+
|
241
|
+
context "using slugs" do
|
242
|
+
|
243
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
244
|
+
lambda {
|
245
|
+
Book.find(:title => "Anti Oedipus")
|
246
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if trying to find a slug that looks like an id" do
|
250
|
+
tricksy = "4f69cd6dfe75bd0cce000003"
|
251
|
+
friend.name = tricksy
|
252
|
+
friend.save!
|
253
|
+
lambda {
|
254
|
+
Friend.find(tricksy)
|
255
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
context "using ids" do
|
261
|
+
|
262
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
263
|
+
lambda {
|
264
|
+
Book.find(friend.id)
|
265
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
266
|
+
end
|
267
|
+
|
268
|
+
context "given a single document" do
|
269
|
+
it "returns the document" do
|
270
|
+
Friend.find(friend.id).should == friend
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "given multiple docuemnts" do
|
275
|
+
it "returns the documents" do
|
276
|
+
Book.find([book.id, book2.id]).should == [book, book2]
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
context "when a key is in use" do
|
281
|
+
it "raises a document not found since you should be finding on the slug" do
|
282
|
+
lambda {
|
283
|
+
Animal.find(animal.id)
|
284
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
285
|
+
end
|
286
|
+
it "is still ok with the use of slug" do
|
287
|
+
Animal.find(animal.to_param).should == animal
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe "#to_param" do
|
293
|
+
context "when called on an existing record with no slug" do
|
294
|
+
before do
|
295
|
+
Book.collection.insert(:title => {I18n.locale.to_s => "Proust and Signs"})
|
296
|
+
end
|
297
|
+
|
298
|
+
it "generates the missing slug" do
|
299
|
+
book = Book.first
|
300
|
+
book.to_param
|
301
|
+
book.reload.to_param.should eql "proust-and-signs"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
describe ".for_unique_slug_for" do
|
307
|
+
it "returns the unique slug" do
|
308
|
+
Book.find_unique_slug_for("A Thousand Plateaus").should eq("a-thousand-plateaus")
|
309
|
+
end
|
310
|
+
|
311
|
+
it "returns the unique slug with a counter if necessary" do
|
312
|
+
Book.create(:title => "A Thousand Plateaus")
|
313
|
+
Book.find_unique_slug_for("A Thousand Plateaus").should eq("a-thousand-plateaus-1")
|
314
|
+
end
|
315
|
+
|
316
|
+
it "returns the unique slug as if it were the provided object" do
|
317
|
+
book = Book.create(:title => "A Thousand Plateaus")
|
318
|
+
Book.find_unique_slug_for("A Thousand Plateaus", :model => book).should eq("a-thousand-plateaus")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "#find_unique_slug_for" do
|
323
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
324
|
+
|
325
|
+
it "returns the unique slug" do
|
326
|
+
book.find_unique_slug_for("Anti Oedipus").should eq("anti-oedipus")
|
327
|
+
end
|
328
|
+
|
329
|
+
it "returns the unique slug with a counter if necessary" do
|
330
|
+
Book.create(:title => "Anti Oedipus")
|
331
|
+
book.find_unique_slug_for("Anti Oedipus").should eq("anti-oedipus-1")
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'pry'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
require File.expand_path('../../lib/mongoid_localized_slug', __FILE__)
|
8
|
+
|
9
|
+
Mongoid.configure do |config|
|
10
|
+
name = 'mongoid_slug_test'
|
11
|
+
config.master = Mongo::Connection.new.db(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/models/*.rb"].each { |f| require f }
|
15
|
+
|
16
|
+
RSpec.configure do |c|
|
17
|
+
c.before(:each) do
|
18
|
+
Mongoid.master.collections.select {|c| c.name !~ /system/ }.each(&:remove)
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid_localized_slug
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Juan Schwindt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: &70295941070740 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70295941070740
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: stringex
|
27
|
+
requirement: &70295941070240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.3'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70295941070240
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bson_ext
|
38
|
+
requirement: &70295941069760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.6'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70295941069760
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pry
|
49
|
+
requirement: &70295941069280 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70295941069280
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &70295941068760 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.9'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70295941068760
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &70295941068220 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.8'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70295941068220
|
80
|
+
description: ! " a \n a ."
|
81
|
+
email:
|
82
|
+
- js@comenta-tv
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- lib/mongoid/localized_slug/criterion.rb
|
88
|
+
- lib/mongoid/localized_slug/version.rb
|
89
|
+
- lib/mongoid/localized_slug.rb
|
90
|
+
- lib/mongoid_localized_slug.rb
|
91
|
+
- LICENSE
|
92
|
+
- README.md
|
93
|
+
- spec/models/animal.rb
|
94
|
+
- spec/models/book.rb
|
95
|
+
- spec/models/friend.rb
|
96
|
+
- spec/models/person.rb
|
97
|
+
- spec/mongoid/slug_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
homepage: http://github.com/hakanensari/mongoid-localized-slug
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
hash: -1768269109729011774
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
segments:
|
121
|
+
- 0
|
122
|
+
hash: -1768269109729011774
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project: mongoid_localized_slug
|
125
|
+
rubygems_version: 1.8.11
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: Generates a URL slug in a Mongoid model for localized fields
|
129
|
+
test_files:
|
130
|
+
- spec/models/animal.rb
|
131
|
+
- spec/models/book.rb
|
132
|
+
- spec/models/friend.rb
|
133
|
+
- spec/models/person.rb
|
134
|
+
- spec/mongoid/slug_spec.rb
|
135
|
+
- spec/spec_helper.rb
|