slug 0.5.7 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +45 -15
- data/Rakefile +1 -29
- data/VERSION.yml +3 -3
- data/lib/slug/slug.rb +43 -35
- data/lib/slug.rb +4 -0
- data/slug.gemspec +40 -41
- data/test/models.rb +13 -4
- data/test/schema.rb +14 -9
- data/test/slug_test.rb +310 -0
- data/test/test_helper.rb +6 -11
- metadata +89 -53
- data/.gitignore +0 -3
- data/test/test_slug.rb +0 -246
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b26d7fca3befe175649b0bdbe3b1e145e775ea77191fbe023f017d907cdfd64c
|
4
|
+
data.tar.gz: '06786f698b25c0e2d061ca77cbafa382e774bb96ded86a7b246c858909e8a1b5'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 248ca7e65bc8ccc47a9cec02c3843f4c1871aa743078627677078c4a52dbde1666226002033c0d5e4a107b27fdb32feb1f51c3d877c815bdb455cdf8dbe87bbf
|
7
|
+
data.tar.gz: dfab8ccf8d2daf51720cbc7ecfb44b9fb3ad5862f85e5e7a44b236170cbb1ac7caeaff52434764d80ed5f62bd0d11b2e104699a1011fe5ca3349e63db868cc16
|
data/README.rdoc
CHANGED
@@ -4,21 +4,21 @@ Slug provides simple, straightforward slugging for your ActiveRecord models.
|
|
4
4
|
|
5
5
|
Slug is based on code from Norman Clarke's fantastic {friendly_id}[http://friendly-id.rubyforge.org] project and Nick Zadrozny's {friendly_identifier}[http://code.google.com/p/friendly-identifier/].
|
6
6
|
|
7
|
-
|
7
|
+
What's different:
|
8
8
|
|
9
9
|
* Unlike friendly_id, slugs are stored directly in your model's table. friendly_id stores its data in a separate sluggable table, which enables cool things like slug versioning--but forces yet another join when trying to do complex find_by_slugs.
|
10
10
|
* Like friendly_id, diacritics (accented characters) are stripped from slug strings.
|
11
|
-
*
|
11
|
+
* The number of options is manageable
|
12
12
|
|
13
13
|
== INSTALLATION
|
14
14
|
|
15
|
-
|
15
|
+
Add the gem to your Gemfile
|
16
16
|
|
17
|
-
|
17
|
+
gem 'slug'
|
18
18
|
|
19
|
-
|
19
|
+
of your rails project.
|
20
20
|
|
21
|
-
|
21
|
+
This is tested with Rails 5.1.4, MRI Ruby 2.4.1
|
22
22
|
|
23
23
|
== USAGE
|
24
24
|
|
@@ -40,11 +40,7 @@ Once your table is migrated, just add
|
|
40
40
|
|
41
41
|
to your ActiveRecord model. <tt>:source_field</tt> is the column you'd like to base the slug on--for example, it might be <tt>:headline</tt>.
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
slug :headline, :column => :web_slug
|
46
|
-
|
47
|
-
would generate a slug based on <tt>headline</tt> and save it to <tt>web_slug</tt>.
|
43
|
+
=== INSTANCE METHOD AS SOURCE COLUMN
|
48
44
|
|
49
45
|
The source column isn't limited to just database attributes--you can specify any instance method. This is handy if you need special formatting on your source column before it's slugged, or if you want to base the slug on several attributes.
|
50
46
|
|
@@ -57,18 +53,52 @@ For example:
|
|
57
53
|
"#{headline}-#{publication_date.year}-#{publication_date.month}"
|
58
54
|
end
|
59
55
|
end
|
60
|
-
end
|
61
56
|
|
62
57
|
would use headline-pub year-pub month as the slug source.
|
63
58
|
|
64
59
|
From here, you can work with your slug as if it were a normal column--<tt>find_by_slug</tt> and named scopes will work as they do for any other column.
|
65
60
|
|
66
|
-
|
67
|
-
|
61
|
+
== OPTIONS
|
62
|
+
There are two options:
|
63
|
+
- <tt>:column</tt> is the slug_column, if for some reason you don't want
|
64
|
+
your slugs stored in a column called "slug"
|
65
|
+
- <tt>:generic_default</tt> is a boolean which, if true, will help you
|
66
|
+
avoid <tt>ActiveRecord::ValidationError</tt> exceptions on the slug column.
|
67
|
+
|
68
|
+
=== COLUMN OPTION
|
69
|
+
If you want to save the slug in a database column that isn't called
|
70
|
+
<tt>slug</tt>, just pass the <tt>:column</tt> option. For example
|
71
|
+
|
72
|
+
slug :headline, :column => :web_slug
|
73
|
+
|
74
|
+
would generate a slug based on <tt>headline</tt> and save it to <tt>web_slug</tt>.
|
75
|
+
|
76
|
+
=== GENERIC DEFAULT OPTION
|
77
|
+
You can avoid <tt>ActiveRecord::ValidationError</tt> exceptions that can
|
78
|
+
occur if the source column is empty, blank, or only contains filtered
|
79
|
+
characters. Simply set <tt>generic_default: true</tt>. For example
|
80
|
+
|
81
|
+
slug :headline, generic_default: true
|
82
|
+
|
83
|
+
will generate a slug based on your model name if the headline is blank.
|
84
|
+
|
85
|
+
This is useful if the source column (e.g. headline) is based on user-generated
|
86
|
+
input or can be blank (nil or empty).
|
87
|
+
|
88
|
+
Some prefer to get the exception in this case. Others want to get a good
|
89
|
+
slug and move on.
|
90
|
+
|
91
|
+
== NOTES
|
92
|
+
* Slug validates presence and uniqueness of the slug column. If you pass something that isn't sluggable as the source (for example, say you set the headline to '---'), a validation error will be set. (See the <tt>:generic_default</tt> option for avoiding this.
|
68
93
|
* Slug doesn't update the slug if the source column changes. If you really need to regenerate the slug, call <tt>@model.set_slug</tt> before save.
|
69
94
|
* If a slug already exists, Slug will automatically append a '-n' suffix to your slug to make it unique. The second instance of a slug is '-1'.
|
70
95
|
* If you don't like the slug formatting or the accented character stripping doesn't work for you, it's easy to override Slug's formatting functions. Check the source for details.
|
71
96
|
|
72
97
|
== AUTHOR
|
73
98
|
|
74
|
-
Ben Koski, ben.koski@gmail.com
|
99
|
+
Ben Koski, ben.koski@gmail.com
|
100
|
+
|
101
|
+
with generous contributions from:
|
102
|
+
- Derek Willis, @derekwillis
|
103
|
+
- others listed in the commit history, which can be found in the
|
104
|
+
{GitHub contributor list}[https://github.com/wbreeze/slug/graphs/contributors]
|
data/Rakefile
CHANGED
@@ -1,23 +1,6 @@
|
|
1
1
|
require 'rake'
|
2
2
|
|
3
|
-
|
4
|
-
require 'jeweler'
|
5
|
-
Jeweler::Tasks.new do |s|
|
6
|
-
s.name = "slug"
|
7
|
-
s.summary = %Q{Simple, straightforward slugs for your ActiveRecord models.}
|
8
|
-
s.email = "ben.koski@gmail.com"
|
9
|
-
s.homepage = "http://github.com/bkoski/slug"
|
10
|
-
s.description = "Simple, straightforward slugs for your ActiveRecord models."
|
11
|
-
s.add_dependency 'activerecord'
|
12
|
-
s.add_dependency 'activesupport'
|
13
|
-
s.authors = ["Ben Koski"]
|
14
|
-
end
|
15
|
-
Jeweler::GemcutterTasks.new
|
16
|
-
rescue LoadError
|
17
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
18
|
-
end
|
19
|
-
|
20
|
-
require 'rake/rdoctask'
|
3
|
+
require 'rdoc/task'
|
21
4
|
Rake::RDocTask.new do |rdoc|
|
22
5
|
rdoc.rdoc_dir = 'rdoc'
|
23
6
|
rdoc.title = 'slug'
|
@@ -33,15 +16,4 @@ Rake::TestTask.new(:test) do |t|
|
|
33
16
|
t.verbose = false
|
34
17
|
end
|
35
18
|
|
36
|
-
begin
|
37
|
-
require 'rcov/rcovtask'
|
38
|
-
Rcov::RcovTask.new do |t|
|
39
|
-
t.libs << 'test'
|
40
|
-
t.test_files = FileList['test/**/*_test.rb']
|
41
|
-
t.verbose = true
|
42
|
-
end
|
43
|
-
rescue LoadError
|
44
|
-
puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
45
|
-
end
|
46
|
-
|
47
19
|
task :default => :test
|
data/VERSION.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
---
|
2
|
-
:
|
3
|
-
:
|
4
|
-
:
|
2
|
+
:major: 4
|
3
|
+
:minor: 0
|
4
|
+
:patch: 1
|
data/lib/slug/slug.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Slug
|
2
2
|
module ClassMethods
|
3
|
-
|
3
|
+
|
4
4
|
# Call this to set up slug handling on an ActiveRecord model.
|
5
5
|
#
|
6
6
|
# Params:
|
@@ -8,37 +8,40 @@ module Slug
|
|
8
8
|
#
|
9
9
|
# Options:
|
10
10
|
# * <tt>:column</tt> - the column the slug will be saved to (defaults to <tt>:slug</tt>)
|
11
|
-
# * <tt>:
|
11
|
+
# * <tt>:validate_uniquness_if</tt> - proc to determine whether uniqueness validation runs, same format as validates_uniquness_of :if
|
12
12
|
#
|
13
13
|
# Slug will take care of validating presence and uniqueness of slug.
|
14
|
-
|
14
|
+
|
15
15
|
# Before create, Slug will generate and assign the slug if it wasn't explicitly set.
|
16
16
|
# Note that subsequent changes to the source column will have no effect on the slug.
|
17
17
|
# If you'd like to update the slug later on, call <tt>@model.set_slug</tt>
|
18
18
|
def slug source, opts={}
|
19
|
-
|
19
|
+
class_attribute :slug_source, :slug_column, :generic_default
|
20
20
|
include InstanceMethods
|
21
|
-
|
21
|
+
|
22
22
|
self.slug_source = source
|
23
|
-
|
24
|
-
self.
|
23
|
+
self.slug_column = opts.fetch(:column, :slug)
|
24
|
+
self.generic_default = opts.fetch(:generic_default, false)
|
25
25
|
|
26
26
|
uniqueness_opts = {}
|
27
|
-
uniqueness_opts.merge!(:if => opts[:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
uniqueness_opts.merge!(:if => opts[:validate_uniqueness_if]) if opts[:validate_uniqueness_if].present?
|
28
|
+
validates_uniqueness_of self.slug_column, uniqueness_opts
|
29
|
+
|
30
|
+
validates_presence_of self.slug_column,
|
31
|
+
message: "cannot be blank. Is #{self.slug_source} sluggable?"
|
32
|
+
validates_format_of self.slug_column,
|
33
|
+
with: /\A[a-z0-9-]+\z/,
|
34
|
+
message: "contains invalid characters. Only downcase letters, numbers, and '-' are allowed."
|
35
|
+
before_validation :set_slug, :on => :create
|
33
36
|
end
|
34
37
|
end
|
35
|
-
|
38
|
+
|
36
39
|
module InstanceMethods
|
37
|
-
|
40
|
+
|
38
41
|
# Sets the slug. Called before create.
|
39
42
|
# By default, set_slug won't change slug if one already exists. Pass :force => true to overwrite.
|
40
43
|
def set_slug(opts={})
|
41
|
-
validate_slug_columns
|
44
|
+
validate_slug_columns
|
42
45
|
return unless self[self.slug_column].blank? || opts[:force] == true
|
43
46
|
|
44
47
|
original_slug = self[self.slug_column]
|
@@ -46,23 +49,24 @@ module Slug
|
|
46
49
|
|
47
50
|
strip_diacritics_from_slug
|
48
51
|
normalize_slug
|
52
|
+
genericize_slug if generic_default
|
49
53
|
assign_slug_sequence unless self[self.slug_column] == original_slug # don't try to increment seq if slug hasn't changed
|
50
54
|
end
|
51
|
-
|
55
|
+
|
52
56
|
# Overwrite existing slug based on current contents of source column.
|
53
57
|
def reset_slug
|
54
58
|
set_slug(:force => true)
|
55
59
|
end
|
56
|
-
|
60
|
+
|
57
61
|
# Overrides to_param to return the model's slug.
|
58
62
|
def to_param
|
59
63
|
self[self.slug_column]
|
60
64
|
end
|
61
|
-
|
65
|
+
|
62
66
|
def self.included(klass)
|
63
67
|
klass.extend(ClassMethods)
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
private
|
67
71
|
# Validates that source and destination methods exist. Invoked at runtime to allow definition
|
68
72
|
# of source/slug methods after <tt>slug</tt> setup in class.
|
@@ -70,7 +74,7 @@ module Slug
|
|
70
74
|
raise ArgumentError, "Source column '#{self.slug_source}' does not exist!" if !self.respond_to?(self.slug_source)
|
71
75
|
raise ArgumentError, "Slug column '#{self.slug_column}' does not exist!" if !self.respond_to?("#{self.slug_column}=")
|
72
76
|
end
|
73
|
-
|
77
|
+
|
74
78
|
# Takes the slug, downcases it and replaces non-word characters with a -.
|
75
79
|
# Feel free to override this method if you'd like different slug formatting.
|
76
80
|
def normalize_slug
|
@@ -84,7 +88,13 @@ module Slug
|
|
84
88
|
s.gsub!(/-+/, '-') # get rid of double-dashes
|
85
89
|
self[self.slug_column] = s.to_s
|
86
90
|
end
|
87
|
-
|
91
|
+
|
92
|
+
def genericize_slug
|
93
|
+
if self[self.slug_column].blank?
|
94
|
+
self[self.slug_column] = self.class.to_s.demodulize.underscore.dasherize
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
88
98
|
# Converts accented characters to their ASCII equivalents and removes them if they have no equivalent.
|
89
99
|
# Override this with a void function if you don't want accented characters to be stripped.
|
90
100
|
def strip_diacritics_from_slug
|
@@ -102,26 +112,24 @@ module Slug
|
|
102
112
|
s = s.pack('U*')
|
103
113
|
self[self.slug_column] = s.to_s
|
104
114
|
end
|
105
|
-
|
115
|
+
|
106
116
|
# If a slug of the same name already exists, this will append '-n' to the end of the slug to
|
107
117
|
# make it unique. The second instance gets a '-1' suffix.
|
108
118
|
def assign_slug_sequence
|
109
119
|
return if self[self.slug_column].blank?
|
110
|
-
|
111
|
-
self[self.slug_column] = "#{self[self.slug_column]}-#{idx}" if idx > 0
|
120
|
+
self[self.slug_column] = next_slug_sequence
|
112
121
|
end
|
113
|
-
|
122
|
+
|
114
123
|
# Returns the next unique index for a slug.
|
115
124
|
def next_slug_sequence
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
current = sequence_match.nil? ? 0 : sequence_match[2].to_i
|
123
|
-
return current + 1
|
125
|
+
assoc = self.class.base_class
|
126
|
+
base_val = slug_val = self[self.slug_column]
|
127
|
+
next_id = 1
|
128
|
+
until assoc.where(self.slug_column => slug_val).first.nil? do
|
129
|
+
slug_val = base_val + "-#{next_id}"
|
130
|
+
next_id = next_id + 1
|
124
131
|
end
|
132
|
+
slug_val
|
125
133
|
end
|
126
134
|
end
|
127
|
-
end
|
135
|
+
end
|
data/lib/slug.rb
CHANGED
@@ -3,4 +3,8 @@ require File.join(File.dirname(__FILE__), 'slug', 'ascii_approximations')
|
|
3
3
|
|
4
4
|
if defined?(ActiveRecord)
|
5
5
|
ActiveRecord::Base.instance_eval { extend Slug::ClassMethods }
|
6
|
+
end
|
7
|
+
|
8
|
+
if defined?(Rails) && Rails.version.to_i < 3
|
9
|
+
raise "This version of slug requires Rails 3 or higher"
|
6
10
|
end
|
data/slug.gemspec
CHANGED
@@ -1,61 +1,60 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# stub: slug 4.0.0 ruby lib
|
5
3
|
|
6
4
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
5
|
+
s.name = "slug"
|
6
|
+
s.version = "4.0.1"
|
9
7
|
|
10
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.require_paths = ["lib"]
|
11
10
|
s.authors = ["Ben Koski"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
11
|
+
s.date = "2018-11-11"
|
12
|
+
s.description = "Simple, straightforward slugs for your ActiveRecord models."
|
13
|
+
s.email = "ben.koski@gmail.com"
|
15
14
|
s.extra_rdoc_files = [
|
16
15
|
"LICENSE",
|
17
|
-
|
16
|
+
"README.rdoc"
|
18
17
|
]
|
19
18
|
s.files = [
|
20
|
-
"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"slug.gemspec",
|
29
|
-
"test/models.rb",
|
30
|
-
"test/schema.rb",
|
31
|
-
"test/test_helper.rb",
|
32
|
-
"test/test_slug.rb"
|
33
|
-
]
|
34
|
-
s.homepage = %q{http://github.com/bkoski/slug}
|
35
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
-
s.require_paths = ["lib"]
|
37
|
-
s.rubygems_version = %q{1.3.5}
|
38
|
-
s.summary = %q{Simple, straightforward slugs for your ActiveRecord models.}
|
39
|
-
s.test_files = [
|
19
|
+
"LICENSE",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"lib/slug.rb",
|
24
|
+
"lib/slug/ascii_approximations.rb",
|
25
|
+
"lib/slug/slug.rb",
|
26
|
+
"slug.gemspec",
|
40
27
|
"test/models.rb",
|
41
|
-
|
42
|
-
|
43
|
-
|
28
|
+
"test/schema.rb",
|
29
|
+
"test/test_helper.rb",
|
30
|
+
"test/slug_test.rb"
|
44
31
|
]
|
32
|
+
s.homepage = "http://github.com/bkoski/slug"
|
33
|
+
s.rubygems_version = "2.2.0"
|
34
|
+
s.summary = "Simple, straightforward slugs for your ActiveRecord models."
|
45
35
|
|
46
36
|
if s.respond_to? :specification_version then
|
47
|
-
|
48
|
-
s.specification_version = 3
|
37
|
+
s.specification_version = 4
|
49
38
|
|
50
|
-
if Gem::Version.new(Gem::
|
51
|
-
s.add_runtime_dependency(%q<
|
52
|
-
s.
|
39
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
40
|
+
s.add_runtime_dependency(%q<rake>, [">= 0"])
|
41
|
+
s.add_development_dependency(%q<minitest>, [">= 0"])
|
42
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
43
|
+
s.add_runtime_dependency(%q<activerecord>, ["> 3.0.0"])
|
44
|
+
s.add_runtime_dependency(%q<activesupport>, ["> 3.0.0"])
|
53
45
|
else
|
54
|
-
s.add_dependency(%q<
|
55
|
-
s.add_dependency(%q<
|
46
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
47
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
48
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
49
|
+
s.add_dependency(%q<activerecord>, ["> 3.0.0"])
|
50
|
+
s.add_dependency(%q<activesupport>, ["> 3.0.0"])
|
56
51
|
end
|
57
52
|
else
|
58
|
-
s.add_dependency(%q<
|
59
|
-
s.add_dependency(%q<
|
53
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
54
|
+
s.add_dependency(%q<minitest>, [">= 0"])
|
55
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
56
|
+
s.add_dependency(%q<activerecord>, ["> 3.0.0"])
|
57
|
+
s.add_dependency(%q<activesupport>, ["> 3.0.0"])
|
60
58
|
end
|
61
59
|
end
|
60
|
+
|
data/test/models.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Used to test slug behavior in general
|
2
2
|
class Article < ActiveRecord::Base
|
3
|
-
slug :headline
|
3
|
+
slug :headline
|
4
|
+
end
|
5
|
+
|
6
|
+
class Storyline < Article
|
4
7
|
end
|
5
8
|
|
6
9
|
# Used to test alternate slug column
|
@@ -14,14 +17,20 @@ class Company < ActiveRecord::Base
|
|
14
17
|
end
|
15
18
|
|
16
19
|
class Post < ActiveRecord::Base
|
17
|
-
slug :headline, :
|
20
|
+
slug :headline, :validate_uniqueness_if => Proc.new { false }
|
18
21
|
end
|
19
22
|
|
20
23
|
# Used to test slugs based on methods rather than database attributes
|
21
24
|
class Event < ActiveRecord::Base
|
22
25
|
slug :title_for_slug
|
23
|
-
|
26
|
+
|
24
27
|
def title_for_slug
|
25
28
|
"#{title}-#{location}"
|
26
29
|
end
|
27
|
-
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Test generation of generic slugs
|
33
|
+
class Generation < ActiveRecord::Base
|
34
|
+
slug :title, generic_default: true
|
35
|
+
end
|
36
|
+
|
data/test/schema.rb
CHANGED
@@ -1,30 +1,35 @@
|
|
1
1
|
ActiveRecord::Schema.define(:version => 1) do
|
2
|
-
|
3
2
|
create_table "articles", :force => true do |t|
|
4
|
-
t.column "headline", "string"
|
3
|
+
t.column "headline", "string", null: false
|
5
4
|
t.column "section", "string"
|
6
|
-
t.column "slug", "string"
|
5
|
+
t.column "slug", "string", null: false
|
6
|
+
t.column "type", "string"
|
7
|
+
t.index ["slug"], name: "index_articles_on_slug", unique: true
|
7
8
|
end
|
8
|
-
|
9
|
+
|
9
10
|
create_table "people", :force => true do |t|
|
10
11
|
t.column "name", "string"
|
11
12
|
t.column "web_slug", "string"
|
12
13
|
end
|
13
|
-
|
14
|
+
|
14
15
|
create_table "companies", :force => true do |t|
|
15
16
|
t.column "name", "string"
|
16
17
|
t.column "slug", "string"
|
17
18
|
end
|
18
|
-
|
19
|
+
|
19
20
|
create_table "posts", :force => true do |t|
|
20
21
|
t.column "headline", "string"
|
21
22
|
t.column "slug", "string"
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
create_table "events", :force => true do |t|
|
25
26
|
t.column "title", "string"
|
26
27
|
t.column "location", "string"
|
27
28
|
t.column "slug", "string"
|
28
29
|
end
|
29
|
-
|
30
|
-
|
30
|
+
|
31
|
+
create_table "generations", :force => true do |t|
|
32
|
+
t.column "title", "string"
|
33
|
+
t.column "slug", "string", null: false
|
34
|
+
end
|
35
|
+
end
|
data/test/slug_test.rb
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
describe Slug do
|
5
|
+
before do
|
6
|
+
Article.delete_all
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'slug' do
|
10
|
+
it "bases slug on specified source column" do
|
11
|
+
article = Article.create!(:headline => 'Test Headline')
|
12
|
+
assert_equal 'test-headline', article.slug
|
13
|
+
end
|
14
|
+
|
15
|
+
it "bases slug on specified source column, even if it is defined as a method rather than database attribute" do
|
16
|
+
article = Event.create!(:title => 'Test Event', :location => 'Portland')
|
17
|
+
assert_equal 'test-event-portland', article.slug
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "slug column" do
|
21
|
+
it "saves slug to 'slug' column by default" do
|
22
|
+
article = Article.create!(:headline => 'Test Headline')
|
23
|
+
assert_equal 'test-headline', article.slug
|
24
|
+
end
|
25
|
+
|
26
|
+
it "saves slug to :column specified in options" do
|
27
|
+
Person.delete_all
|
28
|
+
person = Person.create!(:name => 'Test Person')
|
29
|
+
assert_equal 'test-person', person.web_slug
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "column validations" do
|
35
|
+
it "raises ArgumentError if an invalid source column is passed" do
|
36
|
+
Company.slug(:invalid_source_column)
|
37
|
+
assert_raises(ArgumentError) { Company.create! }
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an ArgumentError if an invalid slug column is passed" do
|
41
|
+
Company.slug(:name, :column => :bad_slug_column)
|
42
|
+
assert_raises(ArgumentError) { Company.create! }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'generates a generic slug' do
|
47
|
+
before do
|
48
|
+
Generation.delete_all
|
49
|
+
end
|
50
|
+
|
51
|
+
it "if source column is empty" do
|
52
|
+
generation = Generation.create!
|
53
|
+
assert_equal 'generation', generation.slug
|
54
|
+
end
|
55
|
+
|
56
|
+
it "if normalization makes source value empty" do
|
57
|
+
generation = Generation.create!(:title => '$$$')
|
58
|
+
assert_equal 'generation', generation.slug
|
59
|
+
end
|
60
|
+
|
61
|
+
it "if source value contains no Latin characters" do
|
62
|
+
generation = Generation.create!(:title => 'ローマ字がない')
|
63
|
+
assert_equal 'generation', generation.slug
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'validation' do
|
68
|
+
it "sets validation error if source column is empty" do
|
69
|
+
article = Article.create
|
70
|
+
assert !article.valid?
|
71
|
+
assert article.errors[:slug]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "sets validation error if normalization makes source value empty" do
|
75
|
+
article = Article.create(:headline => '$$$')
|
76
|
+
assert !article.valid?
|
77
|
+
assert article.errors[:slug]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "validates slug format on save" do
|
81
|
+
article = Article.create!(:headline => 'Test Headline')
|
82
|
+
article.slug = 'A BAD $LUG.'
|
83
|
+
|
84
|
+
assert !article.valid?
|
85
|
+
assert article.errors[:slug].present?
|
86
|
+
end
|
87
|
+
|
88
|
+
it "validates uniqueness of slug by default" do
|
89
|
+
Article.create!(:headline => 'Test Headline')
|
90
|
+
article2 = Article.create!(:headline => 'Test Headline')
|
91
|
+
article2.slug = 'test-headline'
|
92
|
+
|
93
|
+
assert !article2.valid?
|
94
|
+
assert article2.errors[:slug].present?
|
95
|
+
end
|
96
|
+
|
97
|
+
it "uses validate_uniqueness_if proc to decide whether uniqueness validation applies" do
|
98
|
+
Post.create!(:headline => 'Test Headline')
|
99
|
+
article2 = Post.new
|
100
|
+
article2.slug = 'test-headline'
|
101
|
+
|
102
|
+
assert article2.valid?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "doesn't overwrite slug value on create if it was already specified" do
|
107
|
+
a = Article.create!(:headline => 'Test Headline', :slug => 'slug1')
|
108
|
+
assert_equal 'slug1', a.slug
|
109
|
+
end
|
110
|
+
|
111
|
+
it "doesn't update the slug even if the source column changes" do
|
112
|
+
article = Article.create!(:headline => 'Test Headline')
|
113
|
+
article.update_attributes!(:headline => 'New Headline')
|
114
|
+
assert_equal 'test-headline', article.slug
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "resetting a slug" do
|
118
|
+
before do
|
119
|
+
@article = Article.create(:headline => 'test headline')
|
120
|
+
@original_slug = @article.slug
|
121
|
+
end
|
122
|
+
|
123
|
+
it "maintains the same slug if slug column hasn't changed" do
|
124
|
+
@article.reset_slug
|
125
|
+
assert_equal @original_slug, @article.slug
|
126
|
+
end
|
127
|
+
|
128
|
+
it "changes slug if slug column has updated" do
|
129
|
+
@article.headline = "donkey"
|
130
|
+
@article.reset_slug
|
131
|
+
refute_equal(@original_slug, @article.slug)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "maintains sequence" do
|
135
|
+
@existing_article = Article.create!(:headline => 'world cup')
|
136
|
+
@article.headline = "world cup"
|
137
|
+
@article.reset_slug
|
138
|
+
assert_equal 'world-cup-1', @article.slug
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "slug normalization" do
|
143
|
+
before do
|
144
|
+
@article = Article.new
|
145
|
+
end
|
146
|
+
|
147
|
+
it "lowercases strings" do
|
148
|
+
@article.headline = 'AbC'
|
149
|
+
@article.save!
|
150
|
+
assert_equal "abc", @article.slug
|
151
|
+
end
|
152
|
+
|
153
|
+
it "replaces whitespace with dashes" do
|
154
|
+
@article.headline = 'a b'
|
155
|
+
@article.save!
|
156
|
+
assert_equal 'a-b', @article.slug
|
157
|
+
end
|
158
|
+
|
159
|
+
it "replaces 2spaces with 1dash" do
|
160
|
+
@article.headline = 'a b'
|
161
|
+
@article.save!
|
162
|
+
assert_equal 'a-b', @article.slug
|
163
|
+
end
|
164
|
+
|
165
|
+
it "removes punctuation" do
|
166
|
+
@article.headline = 'abc!@#$%^&*•¶§∞¢££¡¿()><?""\':;][]\.,/'
|
167
|
+
@article.save!
|
168
|
+
assert_match 'abc', @article.slug
|
169
|
+
end
|
170
|
+
|
171
|
+
it "strips trailing space" do
|
172
|
+
@article.headline = 'ab '
|
173
|
+
@article.save!
|
174
|
+
assert_equal 'ab', @article.slug
|
175
|
+
end
|
176
|
+
|
177
|
+
it "strips leading space" do
|
178
|
+
@article.headline = ' ab'
|
179
|
+
@article.save!
|
180
|
+
assert_equal 'ab', @article.slug
|
181
|
+
end
|
182
|
+
|
183
|
+
it "strips trailing dashes" do
|
184
|
+
@article.headline = 'ab-'
|
185
|
+
@article.save!
|
186
|
+
assert_match 'ab', @article.slug
|
187
|
+
end
|
188
|
+
|
189
|
+
it "strips leading dashes" do
|
190
|
+
@article.headline = '-ab'
|
191
|
+
@article.save!
|
192
|
+
assert_match 'ab', @article.slug
|
193
|
+
end
|
194
|
+
|
195
|
+
it "remove double-dashes" do
|
196
|
+
@article.headline = 'a--b--c'
|
197
|
+
@article.save!
|
198
|
+
assert_match 'a-b-c', @article.slug
|
199
|
+
end
|
200
|
+
|
201
|
+
it "doesn't modify valid slug strings" do
|
202
|
+
@article.headline = 'a-b-c-d'
|
203
|
+
@article.save!
|
204
|
+
assert_match 'a-b-c-d', @article.slug
|
205
|
+
end
|
206
|
+
|
207
|
+
it "doesn't insert dashes for periods in acronyms, regardless of where they appear in string" do
|
208
|
+
@article.headline = "N.Y.P.D. vs. N.S.A. vs. F.B.I."
|
209
|
+
@article.save!
|
210
|
+
assert_match 'nypd-vs-nsa-vs-fbi', @article.slug
|
211
|
+
end
|
212
|
+
|
213
|
+
it "doesn't insert dashes for apostrophes" do
|
214
|
+
@article.headline = "Thomas Jefferson's Papers"
|
215
|
+
@article.save!
|
216
|
+
assert_match 'thomas-jeffersons-papers', @article.slug
|
217
|
+
end
|
218
|
+
|
219
|
+
it "preserves numbers in slug" do
|
220
|
+
@article.headline = "2010 Election"
|
221
|
+
@article.save!
|
222
|
+
assert_match '2010-election', @article.slug
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "diacritics handling" do
|
227
|
+
before do
|
228
|
+
@article = Article.new
|
229
|
+
end
|
230
|
+
|
231
|
+
it "strips diacritics" do
|
232
|
+
@article.headline = "açaí"
|
233
|
+
@article.save!
|
234
|
+
assert_equal "acai", @article.slug
|
235
|
+
end
|
236
|
+
|
237
|
+
it "strips diacritics correctly " do
|
238
|
+
@article.headline = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
|
239
|
+
@article.save!
|
240
|
+
expected = "aaaaaaaeceeeiiiidnoooooouuuuythssaaaaaaaeceeeeiiiidnoooooouuuuythy".split(//)
|
241
|
+
output = @article.slug.split(//)
|
242
|
+
output.each_index do |i|
|
243
|
+
assert_equal expected[i], output[i]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "sequence handling" do
|
249
|
+
it "doesn't add a sequence if saving first instance of slug" do
|
250
|
+
article = Article.create!(:headline => 'Test Headline')
|
251
|
+
assert_equal 'test-headline', article.slug
|
252
|
+
end
|
253
|
+
|
254
|
+
it "assigns a -1 suffix to the second instance of the slug" do
|
255
|
+
Article.create!(:headline => 'Test Headline')
|
256
|
+
article_2 = Article.create!(:headline => 'Test Headline')
|
257
|
+
assert_equal 'test-headline-1', article_2.slug
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'assigns a -2 suffix to the third instance of the slug containing numbers' do
|
261
|
+
2.times { |i| Article.create! :headline => '11111' }
|
262
|
+
article_3 = Article.create! :headline => '11111'
|
263
|
+
assert_equal '11111-2', article_3.slug
|
264
|
+
end
|
265
|
+
|
266
|
+
it "assigns a -12 suffix to the thirteenth instance of the slug" do
|
267
|
+
12.times { |i| Article.create!(:headline => 'Test Headline') }
|
268
|
+
article_13 = Article.create!(:headline => 'Test Headline')
|
269
|
+
assert_equal 'test-headline-12', article_13.slug
|
270
|
+
|
271
|
+
12.times { |i| Article.create!(:headline => 'latest from lybia') }
|
272
|
+
article_13 = Article.create!(:headline => 'latest from lybia')
|
273
|
+
assert_equal 'latest-from-lybia-12', article_13.slug
|
274
|
+
end
|
275
|
+
|
276
|
+
it "ignores partial matches when calculating sequence" do
|
277
|
+
article_1 = Article.create!(:headline => 'Test Headline')
|
278
|
+
assert_equal 'test-headline', article_1.slug
|
279
|
+
article_2 = Article.create!(:headline => 'Test')
|
280
|
+
assert_equal 'test', article_2.slug
|
281
|
+
article_3 = Article.create!(:headline => 'Test')
|
282
|
+
assert_equal 'test-1', article_3.slug
|
283
|
+
article_4 = Article.create!(:headline => 'Test')
|
284
|
+
assert_equal 'test-2', article_4.slug
|
285
|
+
end
|
286
|
+
|
287
|
+
it "knows about single table inheritance" do
|
288
|
+
article = Article.create!(:headline => 'Test Headline')
|
289
|
+
story = Storyline.create!(:headline => article.headline)
|
290
|
+
assert_equal 'test-headline-1', story.slug
|
291
|
+
end
|
292
|
+
|
293
|
+
it "correctly slugs when a slug is a substring of another" do
|
294
|
+
rap_metal = Article.create!(:headline => 'Rap Metal')
|
295
|
+
assert_equal 'rap-metal', rap_metal.slug
|
296
|
+
|
297
|
+
rap = Article.create!(:headline => 'Rap')
|
298
|
+
assert_equal('rap', rap.slug)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "applies sequence logic correctly when the slug is a substring of another" do
|
302
|
+
rap_metal = Article.create!(:headline => 'Rap Metal')
|
303
|
+
assert_equal 'rap-metal', rap_metal.slug
|
304
|
+
|
305
|
+
Article.create!(:headline => 'Rap')
|
306
|
+
second_rap = Article.create!(:headline => 'Rap')
|
307
|
+
assert_equal('rap-1', second_rap.slug)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,26 +1,21 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'mocha'
|
5
|
-
|
6
|
-
class Test::Unit::TestCase
|
7
|
-
end
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/reporters'
|
8
4
|
|
9
5
|
# You can use "rake test AR_VERSION=2.0.5" to test against 2.0.5, for example.
|
10
6
|
# The default is to use the latest installed ActiveRecord.
|
11
7
|
if ENV["AR_VERSION"]
|
12
8
|
gem 'activerecord', "#{ENV["AR_VERSION"]}"
|
13
|
-
gem 'activesupport', "#{ENV["AR_VERSION"]}"
|
14
9
|
end
|
15
10
|
require 'active_record'
|
16
|
-
|
11
|
+
|
12
|
+
# color test output
|
13
|
+
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(:color => true)]
|
17
14
|
|
18
15
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
19
16
|
require 'slug'
|
20
17
|
|
21
18
|
ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
|
22
|
-
|
23
|
-
load(File.dirname(__FILE__) + "/schema.rb")
|
24
|
-
end
|
19
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
25
20
|
|
26
21
|
require 'models'
|
metadata
CHANGED
@@ -1,48 +1,93 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: slug
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 4.0.1
|
5
5
|
platform: ruby
|
6
|
-
authors:
|
6
|
+
authors:
|
7
7
|
- Ben Koski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
date: 2018-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
17
20
|
type: :runtime
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
21
52
|
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version:
|
24
|
-
|
25
|
-
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
26
70
|
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.0
|
27
76
|
type: :runtime
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
version:
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.0
|
35
83
|
description: Simple, straightforward slugs for your ActiveRecord models.
|
36
84
|
email: ben.koski@gmail.com
|
37
85
|
executables: []
|
38
|
-
|
39
86
|
extensions: []
|
40
|
-
|
41
|
-
extra_rdoc_files:
|
87
|
+
extra_rdoc_files:
|
42
88
|
- LICENSE
|
43
89
|
- README.rdoc
|
44
|
-
files:
|
45
|
-
- .gitignore
|
90
|
+
files:
|
46
91
|
- LICENSE
|
47
92
|
- README.rdoc
|
48
93
|
- Rakefile
|
@@ -53,38 +98,29 @@ files:
|
|
53
98
|
- slug.gemspec
|
54
99
|
- test/models.rb
|
55
100
|
- test/schema.rb
|
101
|
+
- test/slug_test.rb
|
56
102
|
- test/test_helper.rb
|
57
|
-
- test/test_slug.rb
|
58
|
-
has_rdoc: true
|
59
103
|
homepage: http://github.com/bkoski/slug
|
60
104
|
licenses: []
|
61
|
-
|
105
|
+
metadata: {}
|
62
106
|
post_install_message:
|
63
|
-
rdoc_options:
|
64
|
-
|
65
|
-
require_paths:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
66
109
|
- lib
|
67
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
-
requirements:
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
69
112
|
- - ">="
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
version:
|
72
|
-
|
73
|
-
|
74
|
-
requirements:
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
75
117
|
- - ">="
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version:
|
78
|
-
version:
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
79
120
|
requirements: []
|
80
|
-
|
81
121
|
rubyforge_project:
|
82
|
-
rubygems_version:
|
122
|
+
rubygems_version: 2.7.6
|
83
123
|
signing_key:
|
84
|
-
specification_version:
|
124
|
+
specification_version: 4
|
85
125
|
summary: Simple, straightforward slugs for your ActiveRecord models.
|
86
|
-
test_files:
|
87
|
-
- test/models.rb
|
88
|
-
- test/schema.rb
|
89
|
-
- test/test_helper.rb
|
90
|
-
- test/test_slug.rb
|
126
|
+
test_files: []
|
data/.gitignore
DELETED
data/test/test_slug.rb
DELETED
@@ -1,246 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class TestSlug < Test::Unit::TestCase
|
4
|
-
|
5
|
-
def setup
|
6
|
-
Article.delete_all
|
7
|
-
Person.delete_all
|
8
|
-
end
|
9
|
-
|
10
|
-
should "base slug on specified source column" do
|
11
|
-
article = Article.create!(:headline => 'Test Headline')
|
12
|
-
assert_equal 'test-headline', article.slug
|
13
|
-
end
|
14
|
-
|
15
|
-
should "base slug on specified source column, even if it is defined as a method rather than database attribute" do
|
16
|
-
article = Event.create!(:title => 'Test Event', :location => 'Portland')
|
17
|
-
assert_equal 'test-event-portland', article.slug
|
18
|
-
end
|
19
|
-
|
20
|
-
context "slug column" do
|
21
|
-
should "save slug to 'slug' column by default" do
|
22
|
-
article = Article.create!(:headline => 'Test Headline')
|
23
|
-
assert_equal 'test-headline', article.slug
|
24
|
-
end
|
25
|
-
|
26
|
-
should "save slug to :column specified in options" do
|
27
|
-
person = Person.create!(:name => 'Test Person')
|
28
|
-
assert_equal 'test-person', person.web_slug
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
context "column validations" do
|
33
|
-
should "raise ArgumentError if an invalid source column is passed" do
|
34
|
-
Company.slug(:invalid_source_column)
|
35
|
-
assert_raises(ArgumentError) { Company.create! }
|
36
|
-
end
|
37
|
-
|
38
|
-
should "raise an ArgumentError if an invalid slug column is passed" do
|
39
|
-
Company.slug(:name, :column => :bad_slug_column)
|
40
|
-
assert_raises(ArgumentError) { Company.create! }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
should "set validation error if source column is empty" do
|
45
|
-
article = Article.create
|
46
|
-
assert !article.valid?
|
47
|
-
require 'ruby-debug'
|
48
|
-
assert article.errors.on(:slug)
|
49
|
-
end
|
50
|
-
|
51
|
-
should "set validation error if normalization makes source value empty" do
|
52
|
-
article = Article.create(:headline => '$$$')
|
53
|
-
assert !article.valid?
|
54
|
-
assert article.errors.on(:slug)
|
55
|
-
end
|
56
|
-
|
57
|
-
should "not update the slug even if the source column changes" do
|
58
|
-
article = Article.create!(:headline => 'Test Headline')
|
59
|
-
article.update_attributes!(:headline => 'New Headline')
|
60
|
-
assert_equal 'test-headline', article.slug
|
61
|
-
end
|
62
|
-
|
63
|
-
should "validate slug format on save" do
|
64
|
-
article = Article.create!(:headline => 'Test Headline')
|
65
|
-
article.slug = 'A BAD $LUG.'
|
66
|
-
|
67
|
-
assert !article.valid?
|
68
|
-
assert article.errors[:slug].present?
|
69
|
-
end
|
70
|
-
|
71
|
-
should "validate uniqueness of slug by default" do
|
72
|
-
article1 = Article.create!(:headline => 'Test Headline')
|
73
|
-
article2 = Article.create!(:headline => 'Test Headline')
|
74
|
-
article2.slug = 'test-headline'
|
75
|
-
|
76
|
-
assert !article2.valid?
|
77
|
-
assert article2.errors[:slug].present?
|
78
|
-
end
|
79
|
-
|
80
|
-
should "use validate_uniquness_if proc to decide whether uniqueness validation applies" do
|
81
|
-
article1 = Post.create!(:headline => 'Test Headline')
|
82
|
-
article2 = Post.new
|
83
|
-
article2.slug = 'test-headline'
|
84
|
-
|
85
|
-
assert article2.valid?
|
86
|
-
end
|
87
|
-
|
88
|
-
should "not overwrite slug value on create if it was already specified" do
|
89
|
-
a = Article.create!(:headline => 'Test Headline', :slug => 'slug1')
|
90
|
-
assert_equal 'slug1', a.slug
|
91
|
-
end
|
92
|
-
|
93
|
-
context "resetting a slug" do
|
94
|
-
|
95
|
-
setup do
|
96
|
-
@article = Article.create(:headline => 'test headline')
|
97
|
-
@original_slug = @article.slug
|
98
|
-
end
|
99
|
-
|
100
|
-
should "maintain the same slug if slug column hasn't changed" do
|
101
|
-
@article.reset_slug
|
102
|
-
assert_equal @original_slug, @article.slug
|
103
|
-
end
|
104
|
-
|
105
|
-
should "change slug if slug column has updated" do
|
106
|
-
@article.headline = "donkey"
|
107
|
-
@article.reset_slug
|
108
|
-
assert_not_equal @original_slug, @article.slug
|
109
|
-
end
|
110
|
-
|
111
|
-
should "maintain sequence" do
|
112
|
-
@existing_article = Article.create!(:headline => 'world cup')
|
113
|
-
@article.headline = "world cup"
|
114
|
-
@article.reset_slug
|
115
|
-
assert_equal 'world-cup-1', @article.slug
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
|
121
|
-
context "slug normalization" do
|
122
|
-
setup do
|
123
|
-
@article = Article.new
|
124
|
-
end
|
125
|
-
|
126
|
-
should "should lowercase strings" do
|
127
|
-
@article.headline = 'AbC'
|
128
|
-
@article.save!
|
129
|
-
assert_equal "abc", @article.slug
|
130
|
-
end
|
131
|
-
|
132
|
-
should "should replace whitespace with dashes" do
|
133
|
-
@article.headline = 'a b'
|
134
|
-
@article.save!
|
135
|
-
assert_equal 'a-b', @article.slug
|
136
|
-
end
|
137
|
-
|
138
|
-
should "should replace 2spaces with 1dash" do
|
139
|
-
@article.headline = 'a b'
|
140
|
-
@article.save!
|
141
|
-
assert_equal 'a-b', @article.slug
|
142
|
-
end
|
143
|
-
|
144
|
-
should "should remove punctuation" do
|
145
|
-
@article.headline = 'abc!@#$%^&*•¶§∞¢££¡¿()><?""\':;][]\.,/'
|
146
|
-
@article.save!
|
147
|
-
assert_match 'abc', @article.slug
|
148
|
-
end
|
149
|
-
|
150
|
-
should "should strip trailing space" do
|
151
|
-
@article.headline = 'ab '
|
152
|
-
@article.save!
|
153
|
-
assert_equal 'ab', @article.slug
|
154
|
-
end
|
155
|
-
|
156
|
-
should "should strip leading space" do
|
157
|
-
@article.headline = ' ab'
|
158
|
-
@article.save!
|
159
|
-
assert_equal 'ab', @article.slug
|
160
|
-
end
|
161
|
-
|
162
|
-
should "should strip trailing dashes" do
|
163
|
-
@article.headline = 'ab-'
|
164
|
-
@article.save!
|
165
|
-
assert_match 'ab', @article.slug
|
166
|
-
end
|
167
|
-
|
168
|
-
should "should strip leading dashes" do
|
169
|
-
@article.headline = '-ab'
|
170
|
-
@article.save!
|
171
|
-
assert_match 'ab', @article.slug
|
172
|
-
end
|
173
|
-
|
174
|
-
should "remove double-dashes" do
|
175
|
-
@article.headline = 'a--b--c'
|
176
|
-
@article.save!
|
177
|
-
assert_match 'a-b-c', @article.slug
|
178
|
-
end
|
179
|
-
|
180
|
-
should "should not modify valid slug strings" do
|
181
|
-
@article.headline = 'a-b-c-d'
|
182
|
-
@article.save!
|
183
|
-
assert_match 'a-b-c-d', @article.slug
|
184
|
-
end
|
185
|
-
|
186
|
-
should "not insert dashes for periods in acronyms, regardless of where they appear in string" do
|
187
|
-
@article.headline = "N.Y.P.D. vs. N.S.A. vs. F.B.I."
|
188
|
-
@article.save!
|
189
|
-
assert_match 'nypd-vs-nsa-vs-fbi', @article.slug
|
190
|
-
end
|
191
|
-
|
192
|
-
should "not insert dashes for apostrophes" do
|
193
|
-
@article.headline = "Thomas Jefferson's Papers"
|
194
|
-
@article.save!
|
195
|
-
assert_match 'thomas-jeffersons-papers', @article.slug
|
196
|
-
end
|
197
|
-
|
198
|
-
should "preserve numbers in slug" do
|
199
|
-
@article.headline = "2010 Election"
|
200
|
-
@article.save!
|
201
|
-
assert_match '2010-election', @article.slug
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
context "diacritics handling" do
|
206
|
-
setup do
|
207
|
-
@article = Article.new
|
208
|
-
end
|
209
|
-
|
210
|
-
should "should strip diacritics" do
|
211
|
-
@article.headline = "açaí"
|
212
|
-
@article.save!
|
213
|
-
assert_equal "acai", @article.slug
|
214
|
-
end
|
215
|
-
|
216
|
-
should "strip diacritics correctly " do
|
217
|
-
@article.headline = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
|
218
|
-
@article.save!
|
219
|
-
expected = "aaaaaaaeceeeiiiidnoooooouuuuythssaaaaaaaeceeeeiiiidnoooooouuuuythy".split(//)
|
220
|
-
output = @article.slug.split(//)
|
221
|
-
output.each_index do |i|
|
222
|
-
assert_equal expected[i], output[i]
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
context "sequence handling" do
|
228
|
-
should "not add a sequence if saving first instance of slug" do
|
229
|
-
article = Article.create!(:headline => 'Test Headline')
|
230
|
-
assert_equal 'test-headline', article.slug
|
231
|
-
end
|
232
|
-
|
233
|
-
should "assign a -1 suffix to the second instance of the slug" do
|
234
|
-
article_1 = Article.create!(:headline => 'Test Headline')
|
235
|
-
article_2 = Article.create!(:headline => 'Test Headline')
|
236
|
-
assert_equal 'test-headline-1', article_2.slug
|
237
|
-
end
|
238
|
-
|
239
|
-
should "assign a -12 suffix to the thirteenth instance of the slug" do
|
240
|
-
12.times { |i| Article.create!(:headline => 'Test Headline') }
|
241
|
-
article_13 = Article.create!(:headline => 'Test Headline')
|
242
|
-
assert_equal 'test-headline-12', article_13.slug
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
end
|