slugger 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +18 -9
- data/VERSION +1 -1
- data/lib/slugger.rb +53 -32
- data/spec/lib/edge_spec.rb +31 -0
- data/spec/lib/post_spec.rb +22 -0
- data/spec/lib/user_spec.rb +12 -0
- data/spec/schema.rb +12 -7
- data/spec/spec_helper.rb +0 -1
- metadata +10 -6
- data/spec/lib/slugger_spec.rb +0 -8
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Slugger
|
2
2
|
|
3
|
-
Slugger is yet another slug generator. It makes pretty urls for Active Record models. It
|
3
|
+
Slugger is yet another slug generator. It makes pretty urls for Active Record models. Why would the ruby (on rails) community need another slug generating plugin you ask? It doesn't, but I needed one that would jump through some hoops to make sure slugs are unique without setting errors.
|
4
4
|
|
5
5
|
== Installation
|
6
6
|
|
@@ -8,33 +8,42 @@ It's a gem. Either run `gem install slugger` at the command line, or add `gem 's
|
|
8
8
|
|
9
9
|
== Usage
|
10
10
|
|
11
|
-
has_slug :source_column, [
|
11
|
+
has_slug :source_column, [options_hash]
|
12
12
|
|
13
|
-
The default source column is title.
|
13
|
+
The default source column is title and slug column is, well slug. The following two settings are the same:
|
14
|
+
|
15
|
+
has_slug
|
16
|
+
has_slug :title, :slug_column => 'slug'
|
14
17
|
|
15
18
|
:source_column can also be an array, for example, if you wanted to create slugs for an Author model on first and last name:
|
16
19
|
|
17
|
-
|
20
|
+
class Author << ActiveRecord::Base
|
21
|
+
has_slug [:first_name, :last_name]
|
22
|
+
end
|
23
|
+
|
24
|
+
To be sure that the slug will never have a validation error:
|
18
25
|
|
19
|
-
|
26
|
+
class Post << ActiveRecord::Base
|
27
|
+
has_slug :title, :on_conflict => :concat_random_chars
|
28
|
+
end
|
20
29
|
|
21
|
-
Say you have an Episode model that belongs to TVSeries. Every episode will have a pilot, but you don't want the unique validation to build silly looking urls:
|
30
|
+
Say you have an Episode model that belongs to TVSeries. Every episode will have a pilot, but you don't want the unique validation to build silly looking urls: (this doesn't actually work), patches welcome
|
22
31
|
|
23
32
|
class Episode << ActiveRecord::Base
|
24
33
|
belongs_to :series
|
25
34
|
|
26
35
|
has_slug :title, :scope => :series_id
|
27
36
|
end
|
28
|
-
|
29
37
|
|
30
38
|
== Example
|
31
39
|
|
32
|
-
create_table "
|
40
|
+
create_table "collections" do |t|
|
33
41
|
t.string "title"
|
34
42
|
t.string "slug"
|
35
43
|
end
|
44
|
+
add_index(:collections, :slug)
|
36
45
|
|
37
|
-
class
|
46
|
+
class Collection < ActiveRecord::Base
|
38
47
|
has_slug
|
39
48
|
end
|
40
49
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/slugger.rb
CHANGED
@@ -7,35 +7,39 @@ module Slugger
|
|
7
7
|
base.extend(ClassMethods)
|
8
8
|
end
|
9
9
|
module ClassMethods
|
10
|
-
def has_slug(title_column=nil,
|
11
|
-
class_inheritable_accessor :
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
def has_slug(title_column=nil,options={})
|
11
|
+
class_inheritable_accessor :slugger_options
|
12
|
+
default_options = {
|
13
|
+
:title_column => 'title',
|
14
|
+
:slug_column => 'slug',
|
15
|
+
:as_param => true,
|
16
|
+
:substitution_char => '-',
|
17
|
+
:downcase => true,
|
18
|
+
:on_conflict => :error
|
19
|
+
}
|
20
|
+
# :on_conflict can be :error or :concat_random_chars
|
21
|
+
self.slugger_options = default_options.merge(options)
|
22
|
+
self.slugger_options[:title_column] = title_column unless title_column.nil?
|
18
23
|
|
19
|
-
# if columns_hash[
|
20
|
-
#
|
24
|
+
# if columns_hash[slugger_options[:title_column].to_s].nil?
|
21
25
|
# raise ArgumentError, "#{self.name} is missing source column"
|
22
26
|
# end
|
23
|
-
raise ArgumentError, "#{self.name} is missing required
|
27
|
+
raise ArgumentError, "#{self.name} is missing required #{slugger_options[:slug_column]} column" if columns_hash[slugger_options[:slug_column].to_s].nil?
|
24
28
|
|
25
|
-
before_validation :
|
29
|
+
before_validation :permalize, :on => :create
|
26
30
|
|
27
|
-
validates
|
28
|
-
if
|
29
|
-
validates
|
31
|
+
validates slugger_options[:slug_column].to_sym, :presence => true
|
32
|
+
if slugger_options[:scope]
|
33
|
+
validates slugger_options[:slug_column].to_sym, :uniqueness => {:scope => slugger_options[:scope]}
|
30
34
|
else
|
31
|
-
validates
|
35
|
+
validates slugger_options[:slug_column].to_sym, :uniqueness => true
|
32
36
|
end
|
33
37
|
|
34
|
-
send :define_method, :column_to_slug, lambda { self.send(
|
38
|
+
send :define_method, :column_to_slug, lambda { self.send(slugger_options[:title_column]) }
|
35
39
|
|
36
40
|
class << self
|
37
41
|
def find(*args)
|
38
|
-
if self.
|
42
|
+
if self.slugger_options[:as_param] && args.first.is_a?(String)
|
39
43
|
find_by_slug(args)
|
40
44
|
else
|
41
45
|
super(*args)
|
@@ -49,31 +53,48 @@ module Slugger
|
|
49
53
|
module InstanceMethods
|
50
54
|
|
51
55
|
def to_param
|
52
|
-
|
56
|
+
slugger_options[:as_param] ? self.slug : self.id
|
53
57
|
end
|
54
58
|
|
55
59
|
protected
|
56
60
|
|
57
61
|
def permalize
|
58
|
-
return
|
59
|
-
|
62
|
+
return unless self.send("#{self.slugger_options[:slug_column]}").blank?
|
63
|
+
if slugger_options[:title_column].is_a?(Array)
|
64
|
+
s = ""
|
65
|
+
self.slugger_options[:title_column].each do |m|
|
66
|
+
s = "#{s} #{self.send(m)}"
|
67
|
+
end
|
68
|
+
s = Iconv.iconv('ascii//ignore//translit', 'utf-8', s).to_s
|
69
|
+
else
|
70
|
+
s = Iconv.iconv('ascii//ignore//translit', 'utf-8', self.send("#{self.slugger_options[:title_column]}")).to_s
|
71
|
+
end
|
60
72
|
s.gsub!(/\'/, '') # remove '
|
61
73
|
s.gsub!(/\W+/, ' ') # all non-word chars to spaces
|
62
74
|
s.strip! # ohh la la
|
63
|
-
s.downcase!
|
64
|
-
s.gsub!(/\ +/,
|
65
|
-
self.send("#{self.
|
75
|
+
s.downcase! if slugger_options[:downcase]
|
76
|
+
s.gsub!(/\ +/, slugger_options[:substitution_char].to_s) # spaces to dashes, preferred separator char everywhere
|
77
|
+
self.send("#{self.slugger_options[:slug_column]}=", s)
|
78
|
+
slug_conflict_resolution
|
66
79
|
end
|
67
|
-
def
|
68
|
-
self.send("#{self.
|
80
|
+
def slug_conflict_resolution(append=nil)
|
81
|
+
existing = self.class.send(:where, "#{slugger_options[:slug_column]}".to_sym => self.send("#{self.slugger_options[:slug_column]}")).first
|
82
|
+
if existing
|
83
|
+
self.send("slug_conflict_resolution_#{self.slugger_options[:on_conflict]}", append)
|
84
|
+
end
|
69
85
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
86
|
+
def slug_conflict_resolution_concat_random_chars(append)
|
87
|
+
chars = ("a".."z").to_a + ("1".."9").to_a
|
88
|
+
random_chars = Array.new(3, '').collect{chars[rand(chars.size)]}.join
|
89
|
+
self.send("#{self.slugger_options[:slug_column]}=", "#{self.slugger_options[:slug_column]}#{self.slugger_options[:substitution_char]}#{random_chars}")
|
90
|
+
slug_conflict_resolution
|
91
|
+
end
|
92
|
+
def slug_conflict_resolution_error(append)
|
93
|
+
# no op, validatino sets error
|
73
94
|
end
|
74
|
-
|
75
|
-
def
|
76
|
-
|
95
|
+
|
96
|
+
def strip_title
|
97
|
+
self.send("#{self.slugger_options[:title_column]}").strip!
|
77
98
|
end
|
78
99
|
end
|
79
100
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# class Edge < ActiveRecord::Base
|
4
|
+
# has_slug 'name', :slug_column => :slug_name,
|
5
|
+
# :as_param => false,
|
6
|
+
# :substitution_char => "_",
|
7
|
+
# :downcase => false,
|
8
|
+
# :on_conflict => :concat_random_chars
|
9
|
+
# end
|
10
|
+
|
11
|
+
describe Edge do
|
12
|
+
it "should set slug on create" do
|
13
|
+
e = Edge.create(:name => "hello")
|
14
|
+
e.slug_name.should == "hello"
|
15
|
+
end
|
16
|
+
it "should honor downcase false" do
|
17
|
+
e = Edge.create(:name => "YELLING")
|
18
|
+
e.slug_name.should == "YELLING"
|
19
|
+
end
|
20
|
+
it "should honor changing substitution_char" do
|
21
|
+
e = Edge.create(:name => "with spaces")
|
22
|
+
e.slug_name.should == "with_spaces"
|
23
|
+
end
|
24
|
+
it "should concat random chars when conflict" do
|
25
|
+
e = Edge.create(:name => "dupable")
|
26
|
+
e.slug_name.should == "dupable"
|
27
|
+
f = Edge.create(:name => e.slug_name)
|
28
|
+
f.should be_valid
|
29
|
+
f.slug_name.should_not == e.slug_name
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Post do
|
4
|
+
it "should set slug on create" do
|
5
|
+
p = Post.create(:title => "hello world")
|
6
|
+
p.slug.should == "hello-world"
|
7
|
+
end
|
8
|
+
it "should not override given slug" do
|
9
|
+
p = Post.create(:title => "hello world", :slug => "custom-slug")
|
10
|
+
p.slug.should == "custom-slug"
|
11
|
+
end
|
12
|
+
it "should remove apostrophes" do
|
13
|
+
p = Post.create(:title => "apostrop'hes", :slug => "apostrophes")
|
14
|
+
p.slug.should == "apostrophes"
|
15
|
+
end
|
16
|
+
it "should not be valid on duplicate" do
|
17
|
+
p = Post.create(:title => "hello")
|
18
|
+
p.slug.should == "hello"
|
19
|
+
q = Post.create(:title => "hello")
|
20
|
+
q.should_not be_valid
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# class User < ActiveRecord::Base
|
4
|
+
# has_slug [:first_name, :last_name]
|
5
|
+
# end
|
6
|
+
|
7
|
+
describe User do
|
8
|
+
it "should set slug on create when given an array" do
|
9
|
+
u = User.create(:first_name => "Tyler", :last_name => "Durden")
|
10
|
+
u.slug.should == "tyler-durden"
|
11
|
+
end
|
12
|
+
end
|
data/spec/schema.rb
CHANGED
@@ -20,6 +20,11 @@ class CreateSchema < ActiveRecord::Migration
|
|
20
20
|
t.string :slug
|
21
21
|
t.timestamps
|
22
22
|
end
|
23
|
+
|
24
|
+
create_table :edges, :force => true do |t|
|
25
|
+
t.string :name
|
26
|
+
t.string :slug_name
|
27
|
+
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
@@ -33,12 +38,12 @@ end
|
|
33
38
|
|
34
39
|
class User < ActiveRecord::Base
|
35
40
|
has_slug [:first_name, :last_name]
|
41
|
+
end
|
36
42
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
43
|
+
class Edge < ActiveRecord::Base
|
44
|
+
has_slug 'name', :slug_column => :slug_name,
|
45
|
+
:as_param => false,
|
46
|
+
:substitution_char => "_",
|
47
|
+
:downcase => false,
|
48
|
+
:on_conflict => :concat_random_chars
|
44
49
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slugger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Seth Faxon
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-08-
|
18
|
+
date: 2011-08-24 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: activerecord
|
@@ -85,7 +85,9 @@ files:
|
|
85
85
|
- lib/slugger.rb
|
86
86
|
- lib/slugger/version.rb
|
87
87
|
- slugger.gemspec
|
88
|
-
- spec/lib/
|
88
|
+
- spec/lib/edge_spec.rb
|
89
|
+
- spec/lib/post_spec.rb
|
90
|
+
- spec/lib/user_spec.rb
|
89
91
|
- spec/schema.rb
|
90
92
|
- spec/spec_helper.rb
|
91
93
|
- tasks/spec.rake
|
@@ -123,6 +125,8 @@ signing_key:
|
|
123
125
|
specification_version: 3
|
124
126
|
summary: Slugger is yet another slug generator.
|
125
127
|
test_files:
|
126
|
-
- spec/lib/
|
128
|
+
- spec/lib/edge_spec.rb
|
129
|
+
- spec/lib/post_spec.rb
|
130
|
+
- spec/lib/user_spec.rb
|
127
131
|
- spec/schema.rb
|
128
132
|
- spec/spec_helper.rb
|