mongoid-slugify 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|