mongoid-slugify 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/README.rdoc +40 -0
- data/Rakefile +8 -0
- data/lib/mongoid/slugify/version.rb +5 -0
- data/lib/mongoid/slugify.rb +65 -0
- data/lib/mongoid-slugify.rb +1 -0
- data/mongoid-slugify.gemspec +20 -0
- data/spec/mongoid/slugify_spec.rb +233 -0
- data/spec/spec_helper.rb +20 -0
- metadata +82 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
== Installation
|
2
|
+
|
3
|
+
Add mongoid-slugify to your Gemfile:
|
4
|
+
|
5
|
+
gem 'mongoid-slugify'
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
class Product
|
10
|
+
include Mongoid::Document
|
11
|
+
include Mongoid::Slugify
|
12
|
+
|
13
|
+
field :title
|
14
|
+
|
15
|
+
private
|
16
|
+
def generate_slug
|
17
|
+
title.parameterize
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
As you can see, you should make 2 things:
|
22
|
+
|
23
|
+
- include Mongoid::Slugify module to your model
|
24
|
+
- provide a way to generate initial slug for your model (based on any fields, that you want)
|
25
|
+
|
26
|
+
If do those - Mongooid::Slugify will save the generated slug to `slug` field of your model and will care about slug uniqueness
|
27
|
+
(it will append "-1", "-2", etc to your slugs until it finds free one).
|
28
|
+
|
29
|
+
Mongooid::Slugify gives you these additional functions:
|
30
|
+
|
31
|
+
- redefines to_param method, so that it returns slug, if it's present, and model id otherwise
|
32
|
+
- Model.(find_by_slug/find_by_slug!/find_by_slug_or_id/find_by_slug_or_id!) methods.
|
33
|
+
If you don't want to generate slugs for all your existing objects (so that to_param will return model ids) - you should prefer the latter two in your controllers.
|
34
|
+
|
35
|
+
== Warning
|
36
|
+
|
37
|
+
This library will NEVER (at least not in the nearest future) provide a way to generate initial slugs. I just don't need it.
|
38
|
+
Don't try to add this functionality to my library.
|
39
|
+
|
40
|
+
If you need all-out-of-the-box solution - look at Mongoid Slug [https://github.com/hakanensari/mongoid-slug], it's far more full featured and actively developed.
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'mongoid/slugify/version'
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module Mongoid
|
6
|
+
module Slugify
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
field :slug
|
11
|
+
index :slug, :unique => true
|
12
|
+
before_save :assign_slug
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def find_by_slug(slug)
|
17
|
+
where(:slug => slug).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_slug!(slug)
|
21
|
+
find_by_slug(slug) || raise(Mongoid::Errors::DocumentNotFound.new(self, slug))
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_by_slug_or_id(slug_or_id)
|
25
|
+
find_by_slug(slug_or_id) || where(:id => id).first
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_slug_or_id!(slug_or_id)
|
29
|
+
find_by_slug(slug_or_id) || find(slug_or_id)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
def to_param
|
35
|
+
slug || super
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def generate_slug
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_unique_slug
|
44
|
+
current_slug = generate_slug
|
45
|
+
pattern = /^#{Regexp.escape(current_slug)}(?:-(\d+))?$/
|
46
|
+
|
47
|
+
appropriate_class = self.class
|
48
|
+
while (appropriate_class.superclass.include?(Mongoid::Document))
|
49
|
+
appropriate_class = appropriate_class.superclass
|
50
|
+
end
|
51
|
+
|
52
|
+
existing_slugs = appropriate_class.where(:slug => pattern, :_id.ne => _id).only(:slug).map { |record| record.slug }
|
53
|
+
if existing_slugs.count > 0
|
54
|
+
max_counter = existing_slugs.map { |slug| (pattern.match(slug)[1] || 0).to_i }.max
|
55
|
+
current_slug += "-#{max_counter + 1}"
|
56
|
+
end
|
57
|
+
current_slug
|
58
|
+
end
|
59
|
+
|
60
|
+
def assign_slug
|
61
|
+
self.slug = generate_unique_slug
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "mongoid/slugify"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/mongoid/slugify/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Pavel Forkert"]
|
6
|
+
gem.email = ["fxposter@gmail.com"]
|
7
|
+
gem.description = "Provides a simple way to add slug generation to a Mongoid model"
|
8
|
+
gem.summary = "Managing slugs in Mongoid models"
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "mongoid-slugify"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Mongoid::Slugify::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency 'activesupport', '>= 3.0', '< 3.2'
|
19
|
+
gem.add_runtime_dependency 'mongoid', '~> 2.0'
|
20
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
class Author
|
5
|
+
include Mongoid::Document
|
6
|
+
include Mongoid::Slugify
|
7
|
+
field :first_name
|
8
|
+
field :last_name
|
9
|
+
referenced_in :book
|
10
|
+
references_many :characters,
|
11
|
+
:class_name => 'Person',
|
12
|
+
:foreign_key => :author_id
|
13
|
+
|
14
|
+
private
|
15
|
+
def generate_slug
|
16
|
+
[first_name, last_name].reject(&:blank?).join('-').parameterize
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Book
|
21
|
+
include Mongoid::Document
|
22
|
+
include Mongoid::Slugify
|
23
|
+
field :title
|
24
|
+
embeds_many :subjects
|
25
|
+
references_many :authors
|
26
|
+
|
27
|
+
private
|
28
|
+
def generate_slug
|
29
|
+
title.parameterize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ComicBook < Book
|
34
|
+
end
|
35
|
+
|
36
|
+
class Person
|
37
|
+
include Mongoid::Document
|
38
|
+
include Mongoid::Slugify
|
39
|
+
field :name
|
40
|
+
embeds_many :relationships
|
41
|
+
referenced_in :author, :inverse_of => :characters
|
42
|
+
|
43
|
+
private
|
44
|
+
def generate_slug
|
45
|
+
name.parameterize
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Caption
|
50
|
+
include Mongoid::Document
|
51
|
+
include Mongoid::Slugify
|
52
|
+
field :identity
|
53
|
+
field :title
|
54
|
+
field :medium
|
55
|
+
|
56
|
+
private
|
57
|
+
def generate_slug
|
58
|
+
[identity.gsub(/\s*\([^)]+\)/, ''), title].join(' ').parameterize
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Mongoid
|
63
|
+
describe Slugify do
|
64
|
+
let(:book) do
|
65
|
+
Book.create(:title => "A Thousand Plateaus")
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when the object is top-level" do
|
69
|
+
it "generates a slug" do
|
70
|
+
book.to_param.should eql "a-thousand-plateaus"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "updates the slug" do
|
74
|
+
book.title = "Anti Oedipus"
|
75
|
+
book.save
|
76
|
+
book.to_param.should eql "anti-oedipus"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "generates a unique slug by appending a counter to duplicate text" do
|
80
|
+
15.times{ |x|
|
81
|
+
dup = Book.create(:title => book.title)
|
82
|
+
dup.to_param.should eql "a-thousand-plateaus-#{x+1}"
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
it "does not update slug if slugged fields have not changed" do
|
87
|
+
book.save
|
88
|
+
book.to_param.should eql "a-thousand-plateaus"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "does not change slug if slugged fields have changed but generated slug is identical" do
|
92
|
+
book.title = "a thousand plateaus"
|
93
|
+
book.save
|
94
|
+
book.to_param.should eql "a-thousand-plateaus"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "finds by slug" do
|
98
|
+
Book.find_by_slug(book.to_param).should eql book
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when the slug is composed of multiple fields" do
|
103
|
+
let!(:author) do
|
104
|
+
Author.create(
|
105
|
+
:first_name => "Gilles",
|
106
|
+
:last_name => "Deleuze")
|
107
|
+
end
|
108
|
+
|
109
|
+
it "generates a slug" do
|
110
|
+
author.to_param.should eql "gilles-deleuze"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "updates the slug" do
|
114
|
+
author.first_name = "Félix"
|
115
|
+
author.last_name = "Guattari"
|
116
|
+
author.save
|
117
|
+
author.to_param.should eql "felix-guattari"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "generates a unique slug by appending a counter to duplicate text" do
|
121
|
+
dup = Author.create(
|
122
|
+
:first_name => author.first_name,
|
123
|
+
:last_name => author.last_name)
|
124
|
+
dup.to_param.should eql 'gilles-deleuze-1'
|
125
|
+
|
126
|
+
dup2 = Author.create(
|
127
|
+
:first_name => author.first_name,
|
128
|
+
:last_name => author.last_name)
|
129
|
+
|
130
|
+
dup.save
|
131
|
+
dup2.to_param.should eql 'gilles-deleuze-2'
|
132
|
+
end
|
133
|
+
|
134
|
+
it "does not update slug if slugged fields have changed but generated slug is identical" do
|
135
|
+
author.last_name = "DELEUZE"
|
136
|
+
author.save
|
137
|
+
author.to_param.should eql 'gilles-deleuze'
|
138
|
+
end
|
139
|
+
|
140
|
+
it "finds by slug" do
|
141
|
+
Author.find_by_slug("gilles-deleuze").should eql author
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when :slug is given a block" do
|
146
|
+
let(:caption) do
|
147
|
+
Caption.create(:identity => 'Edward Hopper (American, 1882-1967)',
|
148
|
+
:title => 'Soir Bleu, 1914',
|
149
|
+
:medium => 'Oil on Canvas')
|
150
|
+
end
|
151
|
+
|
152
|
+
it "generates a slug" do
|
153
|
+
caption.to_param.should eql 'edward-hopper-soir-bleu-1914'
|
154
|
+
end
|
155
|
+
|
156
|
+
it "updates the slug" do
|
157
|
+
caption.title = 'Road in Maine, 1914'
|
158
|
+
caption.save
|
159
|
+
caption.to_param.should eql "edward-hopper-road-in-maine-1914"
|
160
|
+
end
|
161
|
+
|
162
|
+
it "does not change slug if slugged fields have changed but generated slug is identical" do
|
163
|
+
caption.identity = 'Edward Hopper'
|
164
|
+
caption.save
|
165
|
+
caption.to_param.should eql 'edward-hopper-soir-bleu-1914'
|
166
|
+
end
|
167
|
+
|
168
|
+
it "finds by slug" do
|
169
|
+
Caption.find_by_slug(caption.to_param).should eql caption
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when :index is passed as an argument" do
|
174
|
+
before do
|
175
|
+
Book.collection.drop_indexes
|
176
|
+
Author.collection.drop_indexes
|
177
|
+
end
|
178
|
+
|
179
|
+
it "defines an index on the slug in top-level objects" do
|
180
|
+
Book.create_indexes
|
181
|
+
Book.collection.index_information.should have_key "slug_1"
|
182
|
+
end
|
183
|
+
|
184
|
+
context "when slug is not scoped by a reference association" do
|
185
|
+
it "defines a unique index" do
|
186
|
+
Book.create_indexes
|
187
|
+
Book.index_information["slug_1"]["unique"].should be_true
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "when :index is not passed as an argument" do
|
193
|
+
it "does not define an index on the slug" do
|
194
|
+
Person.create_indexes
|
195
|
+
Person.collection.index_information.should_not have_key "permalink_1"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "when the object has STI" do
|
200
|
+
it "scopes by the superclass" do
|
201
|
+
book = Book.create(:title => "Anti Oedipus")
|
202
|
+
comic_book = ComicBook.create(:title => "Anti Oedipus")
|
203
|
+
comic_book.slug.should_not eql(book.slug)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe ".find_by_slug" do
|
208
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
209
|
+
|
210
|
+
it "returns nil if no document is found" do
|
211
|
+
Book.find_by_slug(:title => "Anti Oedipus").should be_nil
|
212
|
+
end
|
213
|
+
|
214
|
+
it "returns the document if it is found" do
|
215
|
+
Book.find_by_slug(book.slug).should == book
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe ".find_by_slug!" do
|
220
|
+
let!(:book) { Book.create(:title => "A Thousand Plateaus") }
|
221
|
+
|
222
|
+
it "raises a Mongoid::Errors::DocumentNotFound error if no document is found" do
|
223
|
+
lambda {
|
224
|
+
Book.find_by_slug!(:title => "Anti Oedipus")
|
225
|
+
}.should raise_error(Mongoid::Errors::DocumentNotFound)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "returns the document when it is found" do
|
229
|
+
Book.find_by_slug!(book.slug).should == book
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require(:default, :development)
|
3
|
+
|
4
|
+
Mongoid.configure do |config|
|
5
|
+
name = "mongoid_slugify_test"
|
6
|
+
config.master = Mongo::Connection.new.db(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
DatabaseCleaner.strategy = :truncation
|
10
|
+
DatabaseCleaner.orm = :mongoid
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.before :each do
|
14
|
+
DatabaseCleaner.start
|
15
|
+
end
|
16
|
+
|
17
|
+
config.after :each do
|
18
|
+
DatabaseCleaner.clean
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-slugify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pavel Forkert
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-17 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &70314068885540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '3.2'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: *70314068885540
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: mongoid
|
30
|
+
requirement: &70314068884600 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '2.0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: *70314068884600
|
39
|
+
description: Provides a simple way to add slug generation to a Mongoid model
|
40
|
+
email:
|
41
|
+
- fxposter@gmail.com
|
42
|
+
executables: []
|
43
|
+
extensions: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- .gitignore
|
47
|
+
- Gemfile
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- lib/mongoid-slugify.rb
|
51
|
+
- lib/mongoid/slugify.rb
|
52
|
+
- lib/mongoid/slugify/version.rb
|
53
|
+
- mongoid-slugify.gemspec
|
54
|
+
- spec/mongoid/slugify_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: ''
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.10
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Managing slugs in Mongoid models
|
80
|
+
test_files:
|
81
|
+
- spec/mongoid/slugify_spec.rb
|
82
|
+
- spec/spec_helper.rb
|