sluggi 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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +163 -0
- data/Rakefile +10 -0
- data/lib/sluggi/generators/sluggi_generator.rb +14 -0
- data/lib/sluggi/history.rb +37 -0
- data/lib/sluggi/migrations/create_slugs.rb +13 -0
- data/lib/sluggi/slug.rb +19 -0
- data/lib/sluggi/slugged.rb +77 -0
- data/lib/sluggi/version.rb +3 -0
- data/lib/sluggi.rb +5 -0
- data/sluggi.gemspec +30 -0
- data/test/generator_test.rb +17 -0
- data/test/history_test.rb +94 -0
- data/test/slug_test.rb +35 -0
- data/test/slugged_test.rb +105 -0
- data/test/test_helper.rb +37 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b4eacb5e7a6e4854fe19afd19c33334713eec8fd
|
4
|
+
data.tar.gz: 2947d37f4a2e6bee1e7d78c266779ee3dcb6f7f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 462925b7ad95990ad73e6628f02ee22fe456598ceb6078e72992a3fd66f30c045efab0b2ad2b8b3aa58af00aa9ee8defeef875acf09035ceeacd33a42effc1cf
|
7
|
+
data.tar.gz: 026e843376f90f95a87d724bdbe56158e6906c1671aed4d153e8106c585d2cb627b6381265b79743d2234c217ed1a7b576820afedb94f070e4d31e2d086b1dce
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Neighborland, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Sluggi
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/sluggi)
|
4
|
+
|
5
|
+
Sluggi is a simple [friendly_id](https://github.com/norman/friendly_id)-inspired slugging library for ActiveRecord models.
|
6
|
+
|
7
|
+
It provides basic slugs, slug history, and the ability to define multiple slug candidates.
|
8
|
+
|
9
|
+
Sluggi works with Rails 4.0+.
|
10
|
+
|
11
|
+
## Install
|
12
|
+
|
13
|
+
Add this line to your Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'sluggi'
|
17
|
+
```
|
18
|
+
|
19
|
+
Add a string column named `slug` to any models you want to slug. You can generate a migration like so:
|
20
|
+
|
21
|
+
```sh
|
22
|
+
rails generate migration AddSlugToCats slug:string
|
23
|
+
```
|
24
|
+
```ruby
|
25
|
+
# edit the migration to add a unique index:
|
26
|
+
add_index :cats, :slug, unique: true
|
27
|
+
```
|
28
|
+
```sh
|
29
|
+
rake db:migrate
|
30
|
+
```
|
31
|
+
|
32
|
+
To track slug history for any model, you must generate a migration to add the `slugs` table:
|
33
|
+
|
34
|
+
```sh
|
35
|
+
rails generate sluggi
|
36
|
+
rake db:migrate
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
Sluggi is Magic Free(tm). Each slugged model must:
|
42
|
+
|
43
|
+
* Have a column named `slug` (see above).
|
44
|
+
* Include the `Sluggi::Slugged` module
|
45
|
+
* Override the `slug_value` method or the `slug_candidates` method.
|
46
|
+
|
47
|
+
### Simple Slugged Model
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class Cat < ActiveRecord::Base
|
51
|
+
include Sluggi::Slugged
|
52
|
+
|
53
|
+
def slug_value
|
54
|
+
name
|
55
|
+
end
|
56
|
+
|
57
|
+
def slug_value_changed?
|
58
|
+
name_changed?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
cat = Cat.create(name: 'Tuxedo Stan')
|
65
|
+
cat.slug
|
66
|
+
=> 'tuxedo-stan'
|
67
|
+
|
68
|
+
cat.to_param
|
69
|
+
=> 'tuxedo-stan'
|
70
|
+
|
71
|
+
cat_path
|
72
|
+
=> 'cats/tuxedo-stan'
|
73
|
+
|
74
|
+
Cat.find_by_slug('tuxedo-stan')
|
75
|
+
=> cat
|
76
|
+
```
|
77
|
+
|
78
|
+
### Model with Slugged History
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class Cat < ActiveRecord::Base
|
82
|
+
include Sluggi::Slugged
|
83
|
+
include Sluggi::History
|
84
|
+
|
85
|
+
def slug_value
|
86
|
+
name
|
87
|
+
end
|
88
|
+
|
89
|
+
def slug_value_changed?
|
90
|
+
name_changed?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
cat = Cat.create(name: 'Tuxedo Stan')
|
97
|
+
cat.slug
|
98
|
+
=> 'tuxedo-stan'
|
99
|
+
|
100
|
+
cat.name = 'Tuxedo Bob'
|
101
|
+
cat.save
|
102
|
+
cat.slug
|
103
|
+
=> 'tuxedo-bob'
|
104
|
+
|
105
|
+
# use .find_slug! to search slug history:
|
106
|
+
|
107
|
+
Cat.find_slug!('tuxedo-bob')
|
108
|
+
=> cat
|
109
|
+
Cat.find_slug!('tuxedo-stan')
|
110
|
+
=> cat
|
111
|
+
|
112
|
+
# plain finders will not search history:
|
113
|
+
|
114
|
+
Cat.find_by_slug('tuxedo-bob')
|
115
|
+
=> cat
|
116
|
+
|
117
|
+
Cat.find_by_slug('tuxedo-stan')
|
118
|
+
=> RecordNotFound
|
119
|
+
|
120
|
+
```
|
121
|
+
|
122
|
+
### Model with Slug Candidates
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class Cat < ActiveRecord::Base
|
126
|
+
include Sluggi::Slugged
|
127
|
+
|
128
|
+
def name_and_id
|
129
|
+
"#{name}-#{id}"
|
130
|
+
end
|
131
|
+
|
132
|
+
# the first unused value in the list is used
|
133
|
+
def slug_candidates
|
134
|
+
[name, name_and_id]
|
135
|
+
end
|
136
|
+
|
137
|
+
def slug_value_changed?
|
138
|
+
name_changed?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
cat = Cat.create(name: 'Tuxedo Stan')
|
145
|
+
cat.slug
|
146
|
+
=> 'tuxedo-stan'
|
147
|
+
|
148
|
+
cat_2 = Cat.create(name: 'Tuxedo Stan')
|
149
|
+
cat_2.slug
|
150
|
+
=> 'tuxedo-stan-2'
|
151
|
+
cat_2.id
|
152
|
+
=> 2
|
153
|
+
|
154
|
+
```
|
155
|
+
|
156
|
+
## Alternatives
|
157
|
+
|
158
|
+
[override ActiveRecord#to_param](http://guides.rubyonrails.org/active_support_core_extensions.html#to-param)
|
159
|
+
[use ActiveRecord.to_param](https://github.com/rails/rails/pull/12891)
|
160
|
+
[friendly_id](https://github.com/norman/friendly_id)
|
161
|
+
[slug](https://github.com/bkoski/slug)
|
162
|
+
[slugged](https://github.com/Sutto/slugged)
|
163
|
+
[others](https://rubygems.org/search?utf8=%E2%9C%93&query=slug)
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
|
4
|
+
# Copy the migration to create the slugs table.
|
5
|
+
class SluggiGenerator < ActiveRecord::Generators::Base
|
6
|
+
argument :name, type: :string, default: 'required_but_not_used'
|
7
|
+
source_root File.expand_path('../../migrations', __FILE__)
|
8
|
+
|
9
|
+
# Copy the migration template to db/migrate.
|
10
|
+
def copy_files
|
11
|
+
migration_template 'create_slugs.rb', 'db/migrate/create_slugs.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sluggi
|
2
|
+
module History
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :slugs, -> { order('slugs.id DESC') }, class_name: 'Sluggi::Slug', as: :sluggable, dependent: :destroy
|
7
|
+
after_save :create_slug
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def find_slug!(slug)
|
12
|
+
where(slug: slug).first ||
|
13
|
+
find_slugs(slug).first.try(:sluggable) ||
|
14
|
+
raise(ActiveRecord::RecordNotFound)
|
15
|
+
end
|
16
|
+
|
17
|
+
def slug_exists?(slug)
|
18
|
+
where(slug: slug).exists? || find_slugs(slug).exists?
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_slugs(slug)
|
22
|
+
Slug.find_type(slug, base_class.to_s)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def create_slug
|
29
|
+
value = clean_slug(slug_value)
|
30
|
+
return if value.blank?
|
31
|
+
return if slugs.first.try(:slug) == value
|
32
|
+
self.class.find_slugs(value).delete_all # revert to previous slug & put first
|
33
|
+
slugs.create(slug: value)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateSlugs < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :slugs do |t|
|
4
|
+
t.string :slug, null: false
|
5
|
+
t.integer :sluggable_id, null: false
|
6
|
+
t.string :sluggable_type, null: false
|
7
|
+
t.datetime :created_at
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :slugs, [:slug, :sluggable_type], unique: true
|
11
|
+
add_index :slugs, :sluggable_id
|
12
|
+
end
|
13
|
+
end
|
data/lib/sluggi/slug.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sluggi
|
2
|
+
class Slug < ActiveRecord::Base
|
3
|
+
belongs_to :sluggable, polymorphic: true
|
4
|
+
|
5
|
+
validates :slug, presence: true
|
6
|
+
validates :sluggable_id, presence: true
|
7
|
+
validates :sluggable_type, presence: true
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def find_type(slug, sluggable_type)
|
11
|
+
where(slug: slug).where(sluggable_type: sluggable_type).order(id: :desc)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_param
|
16
|
+
slug
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Sluggi
|
2
|
+
module Slugged
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
RESERVED_SLUGS = %w(
|
6
|
+
create
|
7
|
+
edit
|
8
|
+
index
|
9
|
+
new
|
10
|
+
update
|
11
|
+
admin
|
12
|
+
assets
|
13
|
+
images
|
14
|
+
javascripts
|
15
|
+
login
|
16
|
+
logout
|
17
|
+
stylesheets
|
18
|
+
session
|
19
|
+
users
|
20
|
+
)
|
21
|
+
|
22
|
+
NOT_IMPLEMENTED_MESSAGE = 'You must implement #slug_value_changed? and either #slug_value or #slug_candidates'
|
23
|
+
|
24
|
+
included do
|
25
|
+
before_validation :set_slug
|
26
|
+
validates :slug, presence: true, uniqueness: true
|
27
|
+
validates_exclusion_of :slug, in: ->(_) { reserved_slugs }
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def reserved_slugs
|
32
|
+
RESERVED_SLUGS
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_param
|
37
|
+
slug_was
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def clean_slug(value)
|
43
|
+
value.try :parameterize
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_slug
|
47
|
+
return unless new_record? || slug_value_changed?
|
48
|
+
new_slug = clean_slug(slug_value)
|
49
|
+
return if new_slug.blank?
|
50
|
+
self.slug = new_slug
|
51
|
+
end
|
52
|
+
|
53
|
+
# these are generally good to override:
|
54
|
+
|
55
|
+
def slug_value
|
56
|
+
slug_candidates.each do |value|
|
57
|
+
next if value.blank?
|
58
|
+
candidate = clean_slug(value)
|
59
|
+
return candidate if candidate == slug
|
60
|
+
return candidate unless self.class.exists?(slug: candidate)
|
61
|
+
end
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# return an array of candidate slug values
|
66
|
+
# Example:
|
67
|
+
# [first_name, full_name, id_and_full_name]
|
68
|
+
def slug_candidates
|
69
|
+
raise NotImplementedError.new(NOT_IMPLEMENTED_MESSAGE)
|
70
|
+
end
|
71
|
+
|
72
|
+
def slug_value_changed?
|
73
|
+
raise NotImplementedError.new(NOT_IMPLEMENTED_MESSAGE)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/lib/sluggi.rb
ADDED
data/sluggi.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sluggi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sluggi"
|
8
|
+
spec.version = Sluggi::VERSION
|
9
|
+
spec.authors = ["Tee Parham"]
|
10
|
+
spec.email = ["tee@neighborland.com"]
|
11
|
+
spec.summary = %q{Rails Slug Generator}
|
12
|
+
spec.description = %q{A Rails slug generator inspired by friendly_id}
|
13
|
+
spec.homepage = "https://github.com/neighborland/sluggi"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">= 1.9.3"
|
22
|
+
|
23
|
+
spec.add_dependency "activerecord", ">= 4.0"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.1"
|
27
|
+
spec.add_development_dependency "minitest", "~> 4.7"
|
28
|
+
spec.add_development_dependency "sqlite3", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rails", ">= 4.0"
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rails/generators/test_case'
|
3
|
+
|
4
|
+
class GeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests ::SluggiGenerator
|
6
|
+
destination File.expand_path("../../tmp", __FILE__)
|
7
|
+
setup :prepare_destination
|
8
|
+
|
9
|
+
test "generate a migration" do
|
10
|
+
begin
|
11
|
+
run_generator
|
12
|
+
assert_migration "db/migrate/create_slugs"
|
13
|
+
ensure
|
14
|
+
FileUtils.rm_rf self.destination_root
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HistoryTest < MiniTest::Spec
|
4
|
+
class ::Cat < ActiveRecord::Base
|
5
|
+
include Sluggi::Slugged
|
6
|
+
include Sluggi::History
|
7
|
+
|
8
|
+
def slug_value
|
9
|
+
name
|
10
|
+
end
|
11
|
+
|
12
|
+
def slug_value_changed?
|
13
|
+
name_changed?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
Cat.delete_all
|
19
|
+
Sluggi::Slug.delete_all
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".find_slug!" do
|
23
|
+
it "finds current slug" do
|
24
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
25
|
+
assert_equal cat, Cat.find_slug!('tsim-tung-brother-cream')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "finds both current and historical slug" do
|
29
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
30
|
+
cat.name = 'Cream Aberdeen'
|
31
|
+
cat.save!
|
32
|
+
assert_equal cat, Cat.find_slug!('tsim-tung-brother-cream')
|
33
|
+
assert_equal cat, Cat.find_slug!('cream-aberdeen')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises when not found" do
|
37
|
+
assert_raises(ActiveRecord::RecordNotFound) do
|
38
|
+
Cat.find_slug! 'garfield'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ".slug_exists?" do
|
44
|
+
it "exists" do
|
45
|
+
Cat.create(name: 'Tsim Tung Brother Cream')
|
46
|
+
assert Cat.slug_exists?('tsim-tung-brother-cream')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "does not exist" do
|
50
|
+
refute Cat.slug_exists?('tsim-tung-brother-cream')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe ".find_slugs" do
|
55
|
+
it "is empty" do
|
56
|
+
assert_empty Cat.find_slugs('garfield')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "finds historical slug" do
|
60
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
61
|
+
slugs = Cat.find_slugs('tsim-tung-brother-cream')
|
62
|
+
assert_equal 1, slugs.size
|
63
|
+
slug = slugs.first
|
64
|
+
assert_equal 'tsim-tung-brother-cream', slug.slug
|
65
|
+
assert_equal 'Cat', slug.sluggable_type
|
66
|
+
assert_equal cat.id, slug.sluggable_id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ".after_save" do
|
71
|
+
it "creates a slug when model is created" do
|
72
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
73
|
+
assert_equal 1, cat.slugs.size
|
74
|
+
assert_equal 'tsim-tung-brother-cream', cat.slugs.first.slug
|
75
|
+
end
|
76
|
+
|
77
|
+
it "creates a slug when model slug changes" do
|
78
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
79
|
+
cat.name = 'Cream Aberdeen'
|
80
|
+
cat.save!
|
81
|
+
assert_equal 2, cat.slugs.size
|
82
|
+
assert_equal ['cream-aberdeen', 'tsim-tung-brother-cream'], cat.slugs.map(&:slug)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not create a slug when model slug is unchanged" do
|
86
|
+
cat = Cat.create(name: 'Tsim Tung Brother Cream')
|
87
|
+
cat.factoid = 'aka Cream Aberdeen'
|
88
|
+
cat.save!
|
89
|
+
assert_equal 1, cat.slugs.size
|
90
|
+
assert_equal 'tsim-tung-brother-cream', cat.slugs.first.slug
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/test/slug_test.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Sluggi
|
4
|
+
class SlugTest < MiniTest::Spec
|
5
|
+
describe "validation" do
|
6
|
+
it "is not valid" do
|
7
|
+
slug = Slug.new
|
8
|
+
refute slug.valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
it "is valid" do
|
12
|
+
slug = Slug.new(sluggable_type: 'Cat', sluggable_id: 1, slug: 'garfield')
|
13
|
+
assert slug.valid?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".find_type" do
|
18
|
+
it "finds" do
|
19
|
+
slug = Slug.create(sluggable_type: 'Cat', sluggable_id: 1, slug: 'garfield')
|
20
|
+
assert_equal slug, Slug.find_type('garfield', 'Cat').first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#to_param" do
|
25
|
+
it "is nil" do
|
26
|
+
assert_nil Slug.new.to_param
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is slug" do
|
30
|
+
assert_equal 'meow', Slug.new(slug: 'meow').to_param
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SluggedTest < MiniTest::Spec
|
4
|
+
class ::Cat < ActiveRecord::Base
|
5
|
+
include Sluggi::Slugged
|
6
|
+
|
7
|
+
def slug_value
|
8
|
+
name
|
9
|
+
end
|
10
|
+
|
11
|
+
def slug_value_changed?
|
12
|
+
name_changed?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::CandidateCat < ActiveRecord::Base
|
17
|
+
self.table_name = 'cats'
|
18
|
+
include Sluggi::Slugged
|
19
|
+
|
20
|
+
def slug_candidates
|
21
|
+
[nil, name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def slug_value_changed?
|
25
|
+
name_changed?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class ::IncompleteCat < ActiveRecord::Base
|
30
|
+
self.table_name = 'cats'
|
31
|
+
include Sluggi::Slugged
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises when slug_value is not implemented" do
|
35
|
+
assert_raises(NotImplementedError) do
|
36
|
+
IncompleteCat.new.valid?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#to_param" do
|
41
|
+
before do
|
42
|
+
Cat.delete_all
|
43
|
+
@cat = Cat.new(name: 'Tuxedo Stan')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "is nil when new" do
|
47
|
+
assert_nil @cat.to_param
|
48
|
+
end
|
49
|
+
|
50
|
+
it "is the slug when persisted" do
|
51
|
+
@cat.save!
|
52
|
+
assert_equal 'tuxedo-stan', @cat.to_param
|
53
|
+
end
|
54
|
+
|
55
|
+
it "is the old slug when changed but not saved" do
|
56
|
+
@cat.save!
|
57
|
+
@cat.slug = 'ketzel'
|
58
|
+
assert_equal 'tuxedo-stan', @cat.to_param
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ".reserved_slugs" do
|
63
|
+
%w(create login users).each do |word|
|
64
|
+
it "includes #{word}" do
|
65
|
+
assert_includes Cat.reserved_slugs, word
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not include valid slug" do
|
70
|
+
refute_includes Cat.reserved_slugs, 'something'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "validation" do
|
75
|
+
it "reserved slug is invalid" do
|
76
|
+
refute Cat.new(name: 'edit').valid?
|
77
|
+
end
|
78
|
+
|
79
|
+
it "blank slug is invalid" do
|
80
|
+
refute Cat.new(name: '').valid?
|
81
|
+
end
|
82
|
+
|
83
|
+
it "sets slug on validation" do
|
84
|
+
cat = Cat.new(name: 'Prince Chunk')
|
85
|
+
assert cat.valid?
|
86
|
+
assert_equal 'prince-chunk', cat.slug
|
87
|
+
end
|
88
|
+
|
89
|
+
it "is invalid when slug is taken" do
|
90
|
+
Cat.create(slug: 'mrs-chippy')
|
91
|
+
cat = Cat.new(name: 'Mrs. Chippy')
|
92
|
+
refute cat.valid?
|
93
|
+
assert_nil cat.to_param
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "candidates" do
|
98
|
+
it "sets slug from candidates on validation" do
|
99
|
+
cat = CandidateCat.new(name: 'Smokey')
|
100
|
+
assert cat.valid?
|
101
|
+
assert_equal 'smokey', cat.slug
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#require 'coveralls'
|
2
|
+
#Coveralls.wear!
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_record'
|
7
|
+
require 'sqlite3'
|
8
|
+
require 'sluggi'
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger = Logger.new(STDERR) if ENV["VERBOSE"]
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(
|
13
|
+
adapter: "sqlite3",
|
14
|
+
database: ":memory:"
|
15
|
+
)
|
16
|
+
|
17
|
+
unless ActiveRecord::Base.connection.tables.include?('cats')
|
18
|
+
ActiveRecord::Schema.define do
|
19
|
+
create_table :cats do |t|
|
20
|
+
t.datetime :created_at
|
21
|
+
t.string :factoid
|
22
|
+
t.string :name
|
23
|
+
t.string :slug
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table :slugs do |t|
|
27
|
+
t.string :slug, null: false
|
28
|
+
t.integer :sluggable_id, null: false
|
29
|
+
t.string :sluggable_type
|
30
|
+
t.datetime :created_at
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
I18n.enforce_available_locales = true
|
36
|
+
|
37
|
+
# Thanks to this List of Cats: http://en.wikipedia.org/wiki/List_of_cats
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sluggi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tee Parham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.0'
|
97
|
+
description: A Rails slug generator inspired by friendly_id
|
98
|
+
email:
|
99
|
+
- tee@neighborland.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".ruby-version"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- lib/sluggi.rb
|
111
|
+
- lib/sluggi/generators/sluggi_generator.rb
|
112
|
+
- lib/sluggi/history.rb
|
113
|
+
- lib/sluggi/migrations/create_slugs.rb
|
114
|
+
- lib/sluggi/slug.rb
|
115
|
+
- lib/sluggi/slugged.rb
|
116
|
+
- lib/sluggi/version.rb
|
117
|
+
- sluggi.gemspec
|
118
|
+
- test/generator_test.rb
|
119
|
+
- test/history_test.rb
|
120
|
+
- test/slug_test.rb
|
121
|
+
- test/slugged_test.rb
|
122
|
+
- test/test_helper.rb
|
123
|
+
homepage: https://github.com/neighborland/sluggi
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 1.9.3
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.2.1
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Rails Slug Generator
|
147
|
+
test_files:
|
148
|
+
- test/generator_test.rb
|
149
|
+
- test/history_test.rb
|
150
|
+
- test/slug_test.rb
|
151
|
+
- test/slugged_test.rb
|
152
|
+
- test/test_helper.rb
|