has_unique_slug 0.1.3 → 0.1.4
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/Gemfile +0 -1
- data/README.md +19 -28
- data/has_unique_slug.gemspec +3 -1
- data/lib/has_unique_slug.rb +19 -20
- data/lib/has_unique_slug/version.rb +1 -1
- data/spec/has_unique_slug_spec.rb +101 -2
- data/spec/spec_helper.rb +26 -1
- metadata +47 -6
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -9,42 +9,30 @@ Tested and working on Rails 3.1.x
|
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
12
|
-
Assume you have a Post model that has a title and slug column, you can use the following to uniquely parameterize title:
|
13
|
-
|
14
12
|
class Post < ActiveRecord::Base
|
15
13
|
has_unique_slug
|
16
14
|
end
|
17
|
-
|
18
15
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
Ex. Post 1 has title "Sample Post" which would then generate slug "sample-post"
|
23
|
-
Post 2 has also has title "Sample Post" which then would generate slug "sample-post-2"
|
16
|
+
- by default, the column `title` is assumed as the identifier, and `slug` is used to store the unique slug
|
17
|
+
- `title.paramterize` is called to generate the slug unless a slug has already been assigned, in which case parameterize is called on the provided slug
|
18
|
+
- if a slug is not unique in the database, a suffix is appended on the end in the format "-n" where n starts at 2 and progresses ad infinitum until a unique slug is found.
|
24
19
|
|
25
|
-
You can specify which column to use to generate the slug and which column to use to store the slug.
|
20
|
+
You can specify which column to use to generate the slug and which column to use to store the slug. For example:
|
26
21
|
|
27
22
|
class Post < ActiveRecord::Base
|
28
|
-
|
29
|
-
|
23
|
+
has_unique_slug :column => :permalink, :subject => :name
|
24
|
+
# will store the unique slug in the column `permalink` created from `name`
|
30
25
|
end
|
31
26
|
|
32
|
-
|
33
|
-
|
34
|
-
If only 1 argument is given, use that column to store the slug:
|
35
|
-
|
36
|
-
class Post < ActiveRecord::Base
|
37
|
-
has_unique_slug :permalink # Uses the permalink column to store the slug
|
38
|
-
end
|
39
|
-
|
40
|
-
Optionally, a block can be provided to generate the slug:
|
27
|
+
Optionally, a Proc can be used instead of a column name to create the slug:
|
41
28
|
|
42
29
|
class Car < ActiveRecord::Base
|
43
|
-
has_unique_slug {|car| "#{car.year}
|
30
|
+
has_unique_slug :subject => Proc.new {|car| "#{car.year}-#{car.name}"}
|
44
31
|
end
|
45
|
-
|
32
|
+
You do not have to call parameterize on name, this will be done automatically. (You don't even need to add dash, a space will work fine)
|
33
|
+
|
46
34
|
|
47
|
-
You do not have to modify your controller to
|
35
|
+
You do not have to modify your controller to find records:
|
48
36
|
|
49
37
|
class PostsController < ApplicationController
|
50
38
|
|
@@ -56,11 +44,14 @@ You do not have to modify your controller to get this to work:
|
|
56
44
|
end
|
57
45
|
end
|
58
46
|
|
59
|
-
|
60
|
-
|
47
|
+
All the standard url helper methods will still work since `to_param` is overridden to output the slug
|
48
|
+
|
49
|
+
# Ex.
|
50
|
+
post = Post.create! :title => "Sample Post"
|
51
|
+
post_path(post) # /posts/sample-post
|
61
52
|
|
62
53
|
## TODO:
|
63
54
|
|
64
|
-
-
|
65
|
-
-
|
66
|
-
-
|
55
|
+
- Add support for scopes
|
56
|
+
- Add support for database versioning
|
57
|
+
- Consider optimizing the method to ensure a unique slug
|
data/has_unique_slug.gemspec
CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "activerecord"
|
24
|
+
s.add_development_dependency "sqlite3"
|
23
25
|
# s.add_runtime_dependency "rest-client"
|
24
26
|
end
|
data/lib/has_unique_slug.rb
CHANGED
@@ -7,25 +7,26 @@ module HasUniqueSlug
|
|
7
7
|
|
8
8
|
# Builds a slug from the subject_column unless a block is specified.
|
9
9
|
# If a block is specified, the result of the block is returned.
|
10
|
-
def build_slug(record, subject_column
|
11
|
-
(
|
10
|
+
def build_slug(record, subject_column)
|
11
|
+
( subject_column.is_a?(Proc) ? subject_column.call(record) : record[subject_column] ).parameterize
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
15
|
|
16
|
-
def has_unique_slug
|
16
|
+
def has_unique_slug args = {}
|
17
17
|
|
18
|
-
|
19
|
-
options
|
20
|
-
|
21
|
-
slug_column
|
22
|
-
subject_column ||= :title
|
18
|
+
# Setup default options
|
19
|
+
options = { :column => :slug, :subject => :title }
|
20
|
+
options.merge! args
|
21
|
+
slug_column, subject_column = options[:column], options[:subject]
|
23
22
|
|
24
|
-
#
|
25
|
-
|
23
|
+
# Use before_validates otherwise ActiveRecord uniqueness validations on the model will fail. Uniqueness is already guarneteed.
|
24
|
+
# It is not recommend to use a 'validates :slug, :uniqueness => true' validation because that will add unndeeded stress on the
|
25
|
+
# database.
|
26
|
+
before_validation do |record|
|
26
27
|
|
27
|
-
#
|
28
|
-
slug_prefix = record[slug_column].blank? ? build_slug(record, subject_column
|
28
|
+
# Create base slug
|
29
|
+
slug_prefix = record[slug_column].blank? ? build_slug(record, subject_column) : record[slug_column].parameterize
|
29
30
|
|
30
31
|
# Ensure the current slug is unique in the database, if not, make it unqiue
|
31
32
|
test_slug, i = slug_prefix, 1
|
@@ -34,7 +35,7 @@ module HasUniqueSlug
|
|
34
35
|
test_slug = "#{slug_prefix}-#{(i += 1)}"
|
35
36
|
end
|
36
37
|
|
37
|
-
# Set the slug
|
38
|
+
# Set the slug in the record
|
38
39
|
record[slug_column] = test_slug
|
39
40
|
end
|
40
41
|
|
@@ -50,14 +51,11 @@ module HasUniqueSlug
|
|
50
51
|
end
|
51
52
|
EOV
|
52
53
|
|
53
|
-
# Add find method to override ActiveRecord::Base.find
|
54
|
+
# Add find method to override ActiveRecord::Base.find.
|
55
|
+
# Note: find_by_id will still work to search for record by their database id.
|
54
56
|
instance_eval do
|
55
57
|
def find(*args)
|
56
|
-
|
57
|
-
where("#{slug_column} = ?", args.first).first
|
58
|
-
else
|
59
|
-
where("#{slug_column} IN (?)", args)
|
60
|
-
end
|
58
|
+
args.length == 1 ? where(slug_column => args.first).first : where(slug_column => args)
|
61
59
|
end
|
62
60
|
end
|
63
61
|
end
|
@@ -65,7 +63,8 @@ module HasUniqueSlug
|
|
65
63
|
end
|
66
64
|
|
67
65
|
module InstanceMethods
|
68
|
-
|
66
|
+
|
67
|
+
# Override to param method. Outputs the specified column (or slug by default) instead of the id
|
69
68
|
def to_param
|
70
69
|
self.send(self.class.slug_column)
|
71
70
|
end
|
@@ -1,5 +1,104 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
4
|
+
|
5
|
+
def setup_db
|
6
|
+
ActiveRecord::Schema.define(:version => 1) do
|
7
|
+
create_table :standards do |t|
|
8
|
+
t.column :title, :string
|
9
|
+
t.column :slug, :string
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :customs do |t|
|
13
|
+
t.column :name, :string
|
14
|
+
t.column :permalink, :string
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown_db
|
20
|
+
ActiveRecord::Base.connection.tables.each do |t|
|
21
|
+
ActiveRecord::Base.connection.drop_table t
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Standard < ActiveRecord::Base
|
26
|
+
has_unique_slug
|
27
|
+
|
28
|
+
def self.table_name
|
29
|
+
"standards"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Custom < ActiveRecord::Base
|
34
|
+
has_unique_slug :column => :permalink, :subject => :name
|
35
|
+
|
36
|
+
def self.table_name
|
37
|
+
"customs"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Custom2 < ActiveRecord::Base
|
42
|
+
has_unique_slug :column => :permalink, :subject => Proc.new {|record| "zcvf #{record.name} zxvf"}
|
43
|
+
|
44
|
+
def self.table_name
|
45
|
+
"customs"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe HasUniqueSlug do
|
50
|
+
|
51
|
+
before(:all) do
|
52
|
+
setup_db
|
53
|
+
end
|
54
|
+
|
55
|
+
after(:all) do
|
56
|
+
teardown_db
|
57
|
+
end
|
58
|
+
|
59
|
+
it "creates a unique slug" do
|
60
|
+
r = Standard.create! :title => "Sample Record"
|
61
|
+
r.slug.should == "sample-record"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should add incremental column if not unique" do
|
65
|
+
2.upto 5 do |i|
|
66
|
+
r = Standard.create! :title => "Sample Record"
|
67
|
+
r.slug.should == "sample-record-#{i}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not increment the slug if the duplicate is itself" do
|
72
|
+
r = Standard.last
|
73
|
+
slug = r.slug
|
74
|
+
r.save.should be_true
|
75
|
+
r.slug.should == slug
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should update slugs for non-standard implementation" do
|
79
|
+
r = Custom.create! :name => "Sample Record"
|
80
|
+
r.permalink.should == "sample-record"
|
81
|
+
2.upto 5 do |i|
|
82
|
+
r = Custom.create! :name => "Sample Record"
|
83
|
+
r.permalink.should == "sample-record-#{i}"
|
84
|
+
end
|
85
|
+
r = Custom.last
|
86
|
+
slug = r.permalink
|
87
|
+
r.save.should be_true
|
88
|
+
r.permalink.should == slug
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should update slugs based on the block if a block is provided" do
|
92
|
+
r = Custom2.create! :name => "Sample Record"
|
93
|
+
r.permalink.should == "zcvf-sample-record-zxvf"
|
94
|
+
2.upto 5 do |i|
|
95
|
+
r = Custom2.create! :name => "Sample Record"
|
96
|
+
r.permalink.should == "zcvf-sample-record-zxvf-#{i}"
|
97
|
+
end
|
98
|
+
r = Custom2.last
|
99
|
+
slug = r.permalink
|
100
|
+
r.save.should be_true
|
101
|
+
r.permalink.should == slug
|
102
|
+
end
|
103
|
+
|
5
104
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,33 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
|
+
gem 'activerecord'
|
4
|
+
require 'active_record'
|
5
|
+
gem 'sqlite3'
|
6
|
+
require 'sqlite3'
|
3
7
|
|
4
8
|
require 'has_unique_slug' # and any other gems you need
|
5
9
|
|
10
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
11
|
+
|
12
|
+
def setup_db
|
13
|
+
ActiveRecord::Schema.define(:version => 1) do
|
14
|
+
create_table :standard_setup do |t|
|
15
|
+
t.column :title, :string
|
16
|
+
t.column :slug, :string
|
17
|
+
end
|
18
|
+
create_table :customized_setup do |t|
|
19
|
+
t.column :name, :string
|
20
|
+
t.column :permalink, :string
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def teardown_db
|
26
|
+
ActiveRecord::Base.connection.tables.each do |t|
|
27
|
+
ActiveRecord::Base.connection.drop_table t
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
6
31
|
RSpec.configure do |config|
|
7
|
-
|
32
|
+
|
8
33
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_unique_slug
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brendan Stennett
|
@@ -15,9 +15,50 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-10-
|
19
|
-
dependencies:
|
20
|
-
|
18
|
+
date: 2011-10-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: activerecord
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: sqlite3
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
21
62
|
description: Generates a unique slug for use as a drop-in replacement for ids.
|
22
63
|
email:
|
23
64
|
- brendan@unknowncollective.com
|