slug 4.0.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +123 -0
- data/lib/slug.rb +3 -7
- data/lib/slug/slug.rb +51 -88
- data/slug.gemspec +4 -6
- metadata +4 -6
- data/README.rdoc +0 -104
- data/VERSION.yml +0 -4
- data/lib/slug/ascii_approximations.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d86b48722b18d45cf288be1a320bff1550270cd61a0ab2c03dba7cbb67ab9ef
|
4
|
+
data.tar.gz: 1ffb50fcca8eb72cbedb0f002104ce472fc35abf6aaf9635229605b14cb88580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49a6637967aae4804dfe30f1829bbd7cd3dd2d9075337e053d93a23489af88b894275f63787596243b01905eb27b90c7ca0a88ffe60146c780a7eead7aedd451
|
7
|
+
data.tar.gz: 3693e899c785498624a302f92c3bedc6a9432c236c98f3eee924d9aba63c108191b3ab0decb44d1852ecb4093e34c187a5584d5bd082cc9125086c3fec51af8d
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# slug
|
2
|
+
|
3
|
+
Slug provides simple, straightforward slugging for your ActiveRecord models.
|
4
|
+
|
5
|
+
Slug is based on code from Norman Clarke's fantastic [friendly_id](https://github.com/norman/friendly_id) project and Nick Zadrozny's [friendly_identifier](http://code.google.com/p/friendly-identifier/).
|
6
|
+
|
7
|
+
What's different:
|
8
|
+
|
9
|
+
* Unlike friendly_id's more advanced modes, 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
|
+
* Like friendly_id, diacritics (accented characters) are stripped from slug strings.
|
11
|
+
* The number of options is manageable.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add the gem to your Gemfile
|
16
|
+
|
17
|
+
```
|
18
|
+
gem 'slug'
|
19
|
+
```
|
20
|
+
|
21
|
+
of your rails project.
|
22
|
+
|
23
|
+
This is tested with Rails 5.1.4, MRI Ruby 2.4.1
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Creating the database column
|
28
|
+
|
29
|
+
It's up to you to set up the appropriate column in your model. By default, slug saves the slug to a column called 'slug', so in most cases you'll just want to add
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
add_column :my_table, :slug, :string
|
33
|
+
```
|
34
|
+
|
35
|
+
in a migration. You should also add a unque index on the slug field in your migration
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
add_index :model_name, :slug, unique: true
|
39
|
+
```
|
40
|
+
|
41
|
+
Though Slug uses `validates_uniqueness_of` to ensue the uniqueness of your slug, two concurrent INSERTs could try to set the same slug.
|
42
|
+
|
43
|
+
### Model setup
|
44
|
+
|
45
|
+
Once your table is migrated, just add
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
slug :source_field
|
49
|
+
```
|
50
|
+
|
51
|
+
to your ActiveRecord model. `:source_field` is the column you'd like to base the slug on. For example, it might be `:headline`.
|
52
|
+
|
53
|
+
#### Using an instance method as the source column
|
54
|
+
|
55
|
+
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.
|
56
|
+
|
57
|
+
For example:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class Article < ActiveRecord::Base
|
61
|
+
slug :title_for_slug
|
62
|
+
|
63
|
+
def title_for_slug
|
64
|
+
"#{headline}-#{publication_date.year}-#{publication_date.month}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
would use `headline-pub year-pub month` as the slug source.
|
70
|
+
|
71
|
+
From here, you can work with your slug as if it were a normal column. `find_by_slug` and named scopes will work as they do for any other column.
|
72
|
+
|
73
|
+
### Options
|
74
|
+
|
75
|
+
There are two options:
|
76
|
+
|
77
|
+
#### Column
|
78
|
+
|
79
|
+
If you want to save the slug in a database column that isn't called
|
80
|
+
`slug`, just pass the `:column` option. For example:
|
81
|
+
|
82
|
+
```
|
83
|
+
slug :headline, column: :web_slug
|
84
|
+
```
|
85
|
+
|
86
|
+
would generate a slug based on `headline` and save it to `web_slug`.
|
87
|
+
|
88
|
+
#### Generic Default
|
89
|
+
|
90
|
+
If the source column is empty, blank, or only contains filtered
|
91
|
+
characters, you can avoid `ActiveRecord::ValidationError` exceptions
|
92
|
+
by setting `generic_default: true`. For example:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
slug :headline, generic_default: true
|
96
|
+
```
|
97
|
+
|
98
|
+
will generate a slug based on your model name if the headline is blank.
|
99
|
+
|
100
|
+
This is useful if the source column (e.g. headline) is based on user-generated
|
101
|
+
input or can be blank (nil or empty).
|
102
|
+
|
103
|
+
Some prefer to get the exception in this case. Others want to get a good
|
104
|
+
slug and move on.
|
105
|
+
|
106
|
+
## Notes
|
107
|
+
|
108
|
+
* 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. To avoid this, use the `:generic_default` option.
|
109
|
+
* Slug doesn't update the slug if the source column changes. If you really need to regenerate the slug, call `@model.set_slug` before save.
|
110
|
+
* 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'.
|
111
|
+
* 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.
|
112
|
+
|
113
|
+
## Authors
|
114
|
+
|
115
|
+
Ben Koski, ben.koski@gmail.com
|
116
|
+
|
117
|
+
With generous contributions from:
|
118
|
+
* [Derek Willis](http://thescoop.org/)
|
119
|
+
* [Douglas Lovell](https://github.com/wbreeze)
|
120
|
+
* [Paul Battley](https://github.com/threedaymonk)
|
121
|
+
* [Yura Omelchuk](https://github.com/jurgens)
|
122
|
+
* others listed in the
|
123
|
+
[GitHub contributor list](https://github.com/bkoski/slug/graphs/contributors).
|
data/lib/slug.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'slug', 'slug')
|
2
|
-
require File.join(File.dirname(__FILE__), 'slug', 'ascii_approximations')
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
ActiveRecord::Base.instance_eval { include Slug }
|
4
|
+
if defined?(Rails) && Rails.version.to_i < 4
|
5
|
+
raise "This version of slug requires Rails 4 or higher"
|
6
6
|
end
|
7
|
-
|
8
|
-
if defined?(Rails) && Rails.version.to_i < 3
|
9
|
-
raise "This version of slug requires Rails 3 or higher"
|
10
|
-
end
|
data/lib/slug/slug.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
1
3
|
module Slug
|
2
|
-
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
3
7
|
|
4
8
|
# Call this to set up slug handling on an ActiveRecord model.
|
5
9
|
#
|
@@ -17,7 +21,6 @@ module Slug
|
|
17
21
|
# If you'd like to update the slug later on, call <tt>@model.set_slug</tt>
|
18
22
|
def slug source, opts={}
|
19
23
|
class_attribute :slug_source, :slug_column, :generic_default
|
20
|
-
include InstanceMethods
|
21
24
|
|
22
25
|
self.slug_source = source
|
23
26
|
self.slug_column = opts.fetch(:column, :slug)
|
@@ -36,100 +39,60 @@ module Slug
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
validate_slug_columns
|
45
|
-
return unless self[self.slug_column].blank? || opts[:force] == true
|
46
|
-
|
47
|
-
original_slug = self[self.slug_column]
|
48
|
-
self[self.slug_column] = self.send(self.slug_source)
|
49
|
-
|
50
|
-
strip_diacritics_from_slug
|
51
|
-
normalize_slug
|
52
|
-
genericize_slug if generic_default
|
53
|
-
assign_slug_sequence unless self[self.slug_column] == original_slug # don't try to increment seq if slug hasn't changed
|
54
|
-
end
|
42
|
+
# Sets the slug. Called before create.
|
43
|
+
# By default, set_slug won't change slug if one already exists. Pass :force => true to overwrite.
|
44
|
+
def set_slug(opts={})
|
45
|
+
validate_slug_columns
|
46
|
+
return if self[self.slug_column].present? && !opts[:force]
|
55
47
|
|
56
|
-
|
57
|
-
def reset_slug
|
58
|
-
set_slug(:force => true)
|
59
|
-
end
|
48
|
+
self[self.slug_column] = normalize_slug(self.send(self.slug_source))
|
60
49
|
|
61
|
-
#
|
62
|
-
|
63
|
-
self[self.slug_column]
|
50
|
+
# if normalize_slug returned a blank string, try the generic_default handling
|
51
|
+
if generic_default && self[self.slug_column].blank?
|
52
|
+
self[self.slug_column] = self.class.to_s.demodulize.underscore.dasherize
|
64
53
|
end
|
65
54
|
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
# Validates that source and destination methods exist. Invoked at runtime to allow definition
|
72
|
-
# of source/slug methods after <tt>slug</tt> setup in class.
|
73
|
-
def validate_slug_columns
|
74
|
-
raise ArgumentError, "Source column '#{self.slug_source}' does not exist!" if !self.respond_to?(self.slug_source)
|
75
|
-
raise ArgumentError, "Slug column '#{self.slug_column}' does not exist!" if !self.respond_to?("#{self.slug_column}=")
|
76
|
-
end
|
55
|
+
assign_slug_sequence if self.changed_attributes.include?(self.slug_column)
|
56
|
+
end
|
77
57
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
s = ActiveSupport::Multibyte.proxy_class.new(self[self.slug_column]).normalize(:kc)
|
83
|
-
s.downcase!
|
84
|
-
s.strip!
|
85
|
-
s.gsub!(/[^a-z0-9\s-]/, '') # Remove non-word characters
|
86
|
-
s.gsub!(/\s+/, '-') # Convert whitespaces to dashes
|
87
|
-
s.gsub!(/-\z/, '') # Remove trailing dashes
|
88
|
-
s.gsub!(/-+/, '-') # get rid of double-dashes
|
89
|
-
self[self.slug_column] = s.to_s
|
90
|
-
end
|
58
|
+
# Overwrite existing slug based on current contents of source column.
|
59
|
+
def reset_slug
|
60
|
+
set_slug(:force => true)
|
61
|
+
end
|
91
62
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
63
|
+
# Overrides to_param to return the model's slug.
|
64
|
+
def to_param
|
65
|
+
self[self.slug_column]
|
66
|
+
end
|
97
67
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if Slug::ASCII_APPROXIMATIONS[u]
|
106
|
-
a += Slug::ASCII_APPROXIMATIONS[u].unpack('U*')
|
107
|
-
elsif (u < 0x300 || u > 0x036F)
|
108
|
-
a << u
|
109
|
-
end
|
110
|
-
a
|
111
|
-
end
|
112
|
-
s = s.pack('U*')
|
113
|
-
self[self.slug_column] = s.to_s
|
114
|
-
end
|
68
|
+
private
|
69
|
+
# Validates that source and destination methods exist. Invoked at runtime to allow definition
|
70
|
+
# of source/slug methods after <tt>slug</tt> setup in class.
|
71
|
+
def validate_slug_columns
|
72
|
+
raise ArgumentError, "Source column '#{self.slug_source}' does not exist!" if !self.respond_to?(self.slug_source)
|
73
|
+
raise ArgumentError, "Slug column '#{self.slug_column}' does not exist!" if !self.respond_to?("#{self.slug_column}=")
|
74
|
+
end
|
115
75
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
76
|
+
# Takes the slug, downcases it and replaces non-word characters with a -.
|
77
|
+
# Feel free to override this method if you'd like different slug formatting.
|
78
|
+
def normalize_slug(str)
|
79
|
+
return if str.blank?
|
80
|
+
str.gsub!(/[\p{Pc}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]/, '') # Remove punctuation
|
81
|
+
str.parameterize
|
82
|
+
end
|
122
83
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
84
|
+
# If a slug of the same name already exists, this will append '-n' to the end of the slug to
|
85
|
+
# make it unique. The second instance gets a '-1' suffix.
|
86
|
+
def assign_slug_sequence
|
87
|
+
return if self[self.slug_column].blank?
|
88
|
+
assoc = self.class.base_class
|
89
|
+
base_slug = self[self.slug_column]
|
90
|
+
seq = 0
|
91
|
+
|
92
|
+
while assoc.where(self.slug_column => self[self.slug_column]).exists? do
|
93
|
+
seq += 1
|
94
|
+
self[self.slug_column] = "#{base_slug}-#{seq}"
|
133
95
|
end
|
134
96
|
end
|
135
|
-
|
97
|
+
|
98
|
+
end
|
data/slug.gemspec
CHANGED
@@ -3,25 +3,23 @@
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "slug"
|
6
|
-
s.version = "4.0
|
6
|
+
s.version = "4.1.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib"]
|
10
10
|
s.authors = ["Ben Koski"]
|
11
|
-
s.date = "2018-11-
|
11
|
+
s.date = "2018-11-17"
|
12
12
|
s.description = "Simple, straightforward slugs for your ActiveRecord models."
|
13
13
|
s.email = "ben.koski@gmail.com"
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
16
|
-
"README.
|
16
|
+
"README.md"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
19
|
"LICENSE",
|
20
|
-
"README.
|
20
|
+
"README.md",
|
21
21
|
"Rakefile",
|
22
|
-
"VERSION.yml",
|
23
22
|
"lib/slug.rb",
|
24
|
-
"lib/slug/ascii_approximations.rb",
|
25
23
|
"lib/slug/slug.rb",
|
26
24
|
"slug.gemspec",
|
27
25
|
"test/models.rb",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slug
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Koski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -86,14 +86,12 @@ executables: []
|
|
86
86
|
extensions: []
|
87
87
|
extra_rdoc_files:
|
88
88
|
- LICENSE
|
89
|
-
- README.
|
89
|
+
- README.md
|
90
90
|
files:
|
91
91
|
- LICENSE
|
92
|
-
- README.
|
92
|
+
- README.md
|
93
93
|
- Rakefile
|
94
|
-
- VERSION.yml
|
95
94
|
- lib/slug.rb
|
96
|
-
- lib/slug/ascii_approximations.rb
|
97
95
|
- lib/slug/slug.rb
|
98
96
|
- slug.gemspec
|
99
97
|
- test/models.rb
|
data/README.rdoc
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
= slug
|
2
|
-
|
3
|
-
Slug provides simple, straightforward slugging for your ActiveRecord models.
|
4
|
-
|
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
|
-
|
7
|
-
What's different:
|
8
|
-
|
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
|
-
* Like friendly_id, diacritics (accented characters) are stripped from slug strings.
|
11
|
-
* The number of options is manageable
|
12
|
-
|
13
|
-
== INSTALLATION
|
14
|
-
|
15
|
-
Add the gem to your Gemfile
|
16
|
-
|
17
|
-
gem 'slug'
|
18
|
-
|
19
|
-
of your rails project.
|
20
|
-
|
21
|
-
This is tested with Rails 5.1.4, MRI Ruby 2.4.1
|
22
|
-
|
23
|
-
== USAGE
|
24
|
-
|
25
|
-
It's up to you to set up the appropriate column in your model. By default, Slug saves the slug to a column called 'slug', so in most cases you'll just want to add
|
26
|
-
|
27
|
-
t.string :slug
|
28
|
-
|
29
|
-
to your create migration.
|
30
|
-
|
31
|
-
I'd also suggest adding a unique index on the slug field in your migration
|
32
|
-
|
33
|
-
add_index :model_name, :slug, :unique => true
|
34
|
-
|
35
|
-
Though Slug uses validates_uniqueness_of to ensue the uniqueness of your slug, two concurrent INSERTs could conceivably try to set the same slug. Unlikely, but it's worth adding a unique index as a backstop.
|
36
|
-
|
37
|
-
Once your table is migrated, just add
|
38
|
-
|
39
|
-
slug :source_field
|
40
|
-
|
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
|
-
|
43
|
-
=== INSTANCE METHOD AS SOURCE COLUMN
|
44
|
-
|
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.
|
46
|
-
|
47
|
-
For example:
|
48
|
-
|
49
|
-
class Article < ActiveRecord::Base
|
50
|
-
slug :title_for_slug
|
51
|
-
|
52
|
-
def title_for_slug
|
53
|
-
"#{headline}-#{publication_date.year}-#{publication_date.month}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
would use headline-pub year-pub month as the slug source.
|
58
|
-
|
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.
|
60
|
-
|
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.
|
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.
|
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'.
|
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.
|
96
|
-
|
97
|
-
== AUTHOR
|
98
|
-
|
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/VERSION.yml
DELETED