speaking_id 0.1.0 → 0.2.0
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/README.rdoc +36 -24
- data/Rakefile +4 -5
- data/lib/speaking_id/ascii_approximations.rb +30 -0
- data/lib/speaking_id/core_extensions.rb +24 -0
- data/lib/speaking_id/speaking_id.rb +39 -16
- data/lib/speaking_id.rb +3 -4
- data/rails/init.rb +1 -0
- data/test/core_extensions_test.rb +22 -0
- data/test/schema.rb +15 -0
- data/test/speaking_id_test.rb +50 -4
- data/test/test_helper.rb +17 -1
- metadata +21 -11
- data/CHANGELOG.rdoc +0 -3
- data/init.rb +0 -1
data/README.rdoc
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
= SpeakingId
|
2
2
|
|
3
|
-
|
3
|
+
Lightweight Ruby on Rails gem to handle slugs for Active Record objects.
|
4
4
|
|
5
5
|
== Installation
|
6
6
|
|
7
|
-
|
7
|
+
=== Rails 3
|
8
|
+
|
9
|
+
Add the gem to your project's Gemfile as follows:
|
10
|
+
|
11
|
+
gem 'speaking_id'
|
12
|
+
|
13
|
+
Then install the gem by running:
|
14
|
+
|
15
|
+
bundle install
|
16
|
+
|
17
|
+
=== Rails 2
|
18
|
+
|
19
|
+
In Rails 2 you can set it up in your environment.rb file.
|
8
20
|
|
9
21
|
config.gem "speaking_id"
|
10
22
|
|
@@ -20,33 +32,33 @@ Alternatively you can install it as a Rails plugin.
|
|
20
32
|
|
21
33
|
In your models:
|
22
34
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
has_slug :name
|
37
|
+
end
|
38
|
+
|
39
|
+
class Project < ActiveRecord::Base
|
40
|
+
has_random_slug
|
41
|
+
end
|
30
42
|
|
31
43
|
In your migrations:
|
32
44
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
class AddSpeakingIdToUserAndProject < ActiveRecord::Migration
|
46
|
+
def self.up
|
47
|
+
add_column :users, :slug, :string
|
48
|
+
add_column :project, :slug, :string
|
49
|
+
|
50
|
+
add_index :users, :slug, :unique => true
|
51
|
+
add_index :project, :slug, :unique => true
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.down
|
55
|
+
remove_column :users, :slug
|
56
|
+
remove_column :project, :slug
|
57
|
+
end
|
58
|
+
end
|
47
59
|
|
48
60
|
By default, SpeakingId uses a column called "slug". If you want to use a different column name, just pass the +:column+ option:
|
49
61
|
|
50
|
-
|
62
|
+
has_slug :title, :column => :column_name
|
51
63
|
|
52
64
|
This works also with the +has_random_slug+ method.
|
data/Rakefile
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
require 'rake'
|
2
1
|
require 'rake/testtask'
|
3
2
|
|
3
|
+
desc 'Default: run unit tests.'
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
desc 'Test the speaking_id plugin.'
|
4
7
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs << 'lib'
|
6
8
|
t.libs << 'test'
|
7
9
|
t.pattern = 'test/**/*_test.rb'
|
8
|
-
t.verbose = true
|
9
10
|
end
|
10
|
-
|
11
|
-
task :default => :test
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SpeakingId
|
2
|
+
ASCII_APPROXIMATIONS = {
|
3
|
+
196 => [65, 101], # Ä => Ae
|
4
|
+
214 => [79, 101], # Ö => Oe
|
5
|
+
220 => [85, 101], # Ü => Ue
|
6
|
+
223 => [115, 115], # ß => ss
|
7
|
+
228 => [97, 101], # ä => ae
|
8
|
+
246 => [111, 101], # ö => oe
|
9
|
+
252 => [117, 101] # ü => ue
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def replace_known_special_chars(chars)
|
14
|
+
chars = ActiveSupport::Multibyte.proxy_class.new(chars)
|
15
|
+
chars = chars.normalize(:kc).unpack('U*')
|
16
|
+
|
17
|
+
chars = chars.inject([]) do |result, char|
|
18
|
+
result << approximate_char(char)
|
19
|
+
end.flatten.pack('U*')
|
20
|
+
end
|
21
|
+
|
22
|
+
def approximate_char(char)
|
23
|
+
if ASCII_APPROXIMATIONS.include? char
|
24
|
+
return ASCII_APPROXIMATIONS[char]
|
25
|
+
end
|
26
|
+
char
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
String.class_eval do
|
2
|
+
# Replaces known special characters and removes unknown characters in a string
|
3
|
+
# so that it may be used as part of a 'pretty' URL.
|
4
|
+
def normalize(sep = '-')
|
5
|
+
return if self.blank?
|
6
|
+
|
7
|
+
s = SpeakingId::replace_known_special_chars(self)
|
8
|
+
|
9
|
+
# Remove anything non-ASCII entirely.
|
10
|
+
s = s.gsub(/[^\x00-\x7F]+/, '')
|
11
|
+
|
12
|
+
# Remove non-word characters.
|
13
|
+
s = s.gsub(/[^\w_ \-]+/i, '')
|
14
|
+
|
15
|
+
# Convert whitespaces to separator.
|
16
|
+
s = s.gsub(/[ \-]+/i, sep)
|
17
|
+
|
18
|
+
# Remove leading/trailing separators.
|
19
|
+
s = s.gsub(/^(\_|\-)|(\_|\-)$/i, '')
|
20
|
+
|
21
|
+
s.downcase
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -1,31 +1,48 @@
|
|
1
1
|
module SpeakingId
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
def self.included(base)
|
3
|
+
base.send :extend, ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def has_slug(source, options = {})
|
8
|
+
speaking_id(options)
|
9
|
+
|
10
|
+
class_inheritable_accessor :slug_source
|
5
11
|
|
6
|
-
class_inheritable_accessor :slug_source, :slug_column
|
7
|
-
|
8
12
|
self.slug_source = source
|
9
|
-
self.slug_column = args.has_key?(:column) ? args[:column] : :slug
|
10
13
|
|
11
14
|
before_save :create_slug
|
12
15
|
end
|
13
16
|
|
14
|
-
def has_random_slug
|
15
|
-
|
17
|
+
def has_random_slug(options = {})
|
18
|
+
speaking_id(options)
|
19
|
+
|
20
|
+
before_create :create_random_slug
|
21
|
+
end
|
22
|
+
|
23
|
+
def speaking_id(options = {})
|
24
|
+
send :include, InstanceMethods
|
16
25
|
|
17
26
|
class_inheritable_accessor :slug_column
|
18
|
-
|
19
|
-
self.slug_column = args.has_key?(:column) ? args[:column] : :slug
|
20
27
|
|
21
|
-
|
28
|
+
self.slug_column = (options[:column] || :slug).to_s
|
29
|
+
|
30
|
+
validates_uniqueness_of self.slug_column
|
22
31
|
end
|
32
|
+
|
33
|
+
send :protected, :speaking_id
|
23
34
|
end
|
24
35
|
|
25
36
|
module InstanceMethods
|
26
37
|
def create_slug
|
38
|
+
# Only creates a slug when the Active Record object is unsaved or got changed.
|
39
|
+
return unless self.instance_eval("#{self.slug_source}_changed?")
|
40
|
+
|
27
41
|
begin
|
28
|
-
|
42
|
+
# Normalizes the slug source column or creates a random slug when blank.
|
43
|
+
self[self.slug_column] = self[self.slug_source].normalize
|
44
|
+
return create_random_slug if self[self.slug_column].blank?
|
45
|
+
|
29
46
|
self[self.slug_column] << ((counter ||= 1) == 1 ? nil : counter).to_s
|
30
47
|
counter += 1
|
31
48
|
end while slug_taken?
|
@@ -43,12 +60,18 @@ module SpeakingId
|
|
43
60
|
|
44
61
|
private
|
45
62
|
|
46
|
-
def create_random_token
|
63
|
+
def create_random_token(limit = 3)
|
47
64
|
ActiveSupport::SecureRandom.hex(limit).upcase
|
48
65
|
end
|
49
|
-
|
66
|
+
|
50
67
|
def slug_taken?
|
51
|
-
|
68
|
+
if Rails::VERSION::MAJOR == 2
|
69
|
+
self.class.first :conditions => {self.slug_column.to_sym => self[self.slug_column]}
|
70
|
+
else
|
71
|
+
self.class.where(self.slug_column => self[self.slug_column]).first
|
72
|
+
end
|
52
73
|
end
|
53
74
|
end
|
54
|
-
end
|
75
|
+
end
|
76
|
+
|
77
|
+
ActiveRecord::Base.send :include, SpeakingId
|
data/lib/speaking_id.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'speaking_id/ascii_approximations'
|
2
|
+
require 'speaking_id/core_extensions'
|
3
|
+
require 'speaking_id/speaking_id'
|
2
4
|
|
3
|
-
if defined?(ActiveRecord)
|
4
|
-
ActiveRecord::Base.instance_eval {extend SpeakingId::ClassMethods}
|
5
|
-
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'speaking_id'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class SpeakingIdTest < ActiveSupport::TestCase
|
6
|
+
test 'should normalize a simple string' do
|
7
|
+
assert_equal 'the-sandals', 'The Sandals'.normalize
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'should normalize special characters' do
|
11
|
+
assert_equal 'oeaeue-oeaeue-ss', ' ÖÄÜ öäü ß %@: .&? ---'.normalize
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'should normalize a string using underscores as separator' do
|
15
|
+
assert_equal 'a_b_c', 'A B C ...'.normalize('_')
|
16
|
+
end
|
17
|
+
|
18
|
+
test 'should normalize and return an empty string' do
|
19
|
+
assert ' &?=... '.normalize.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/test/schema.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table :articles, :force => true do |t|
|
3
|
+
t.string :title
|
4
|
+
t.string :slug
|
5
|
+
t.text :text
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :users, :force => true do |t|
|
10
|
+
t.string :email
|
11
|
+
t.string :random_slug
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
data/test/speaking_id_test.rb
CHANGED
@@ -1,8 +1,54 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
5
|
+
class Article < ActiveRecord::Base
|
6
|
+
has_slug :title
|
7
|
+
end
|
8
|
+
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
has_random_slug :column => :random_slug
|
11
|
+
end
|
12
|
+
|
3
13
|
class SpeakingIdTest < ActiveSupport::TestCase
|
4
|
-
|
5
|
-
|
6
|
-
|
14
|
+
load_schema
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@article = Article.find_or_create_by_title('The Sandals')
|
7
18
|
end
|
8
|
-
|
19
|
+
|
20
|
+
test 'should not change the slug' do
|
21
|
+
assert_equal 'the-sandals', @article.slug
|
22
|
+
@article.text = 'The Sandals make great music.'
|
23
|
+
@article.save
|
24
|
+
assert_equal 'the-sandals', @article.slug
|
25
|
+
end
|
26
|
+
|
27
|
+
test 'should change the slug' do
|
28
|
+
assert_equal 'the-sandals', @article.slug
|
29
|
+
@article.title = 'Nico & Rushad'
|
30
|
+
@article.save
|
31
|
+
assert_equal 'nico-rushad', @article.slug
|
32
|
+
end
|
33
|
+
|
34
|
+
test 'should increase the slug' do
|
35
|
+
article = Article.create(:title => 'The Sandals')
|
36
|
+
assert_equal 'the-sandals2', article.slug
|
37
|
+
end
|
38
|
+
|
39
|
+
test 'should increase the slug another time' do
|
40
|
+
article = Article.create(:title => 'The Sandals')
|
41
|
+
assert_equal 'the-sandals3', article.slug
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'should generate a random slug' do
|
45
|
+
@article.title = ''
|
46
|
+
@article.save
|
47
|
+
assert_equal 6, @article.slug.length
|
48
|
+
end
|
49
|
+
|
50
|
+
test 'should create an user with a random slug' do
|
51
|
+
user = User.create(:email => 'test@example.com')
|
52
|
+
assert_equal 6, user.random_slug.length
|
53
|
+
end
|
54
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,3 +1,19 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'test/unit'
|
2
3
|
require 'active_support'
|
3
|
-
require '
|
4
|
+
require 'active_record'
|
5
|
+
require 'sqlite3'
|
6
|
+
|
7
|
+
RAILS_ROOT = File.dirname(__FILE__) + '/../../../..'
|
8
|
+
require File.expand_path(File.join(RAILS_ROOT, 'config/environment.rb'))
|
9
|
+
|
10
|
+
def load_schema
|
11
|
+
ActiveRecord::Base.establish_connection({
|
12
|
+
'adapter' => 'sqlite3',
|
13
|
+
'database' => File.dirname(__FILE__) + '/speaking_id.sqlite3'
|
14
|
+
})
|
15
|
+
|
16
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
17
|
+
require File.dirname(__FILE__) + '/../rails/init.rb'
|
18
|
+
end
|
19
|
+
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speaking_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Martin Jagusch
|
@@ -9,11 +14,11 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-04-06 00:00:00 +02:00
|
13
18
|
default_executable:
|
14
19
|
dependencies: []
|
15
20
|
|
16
|
-
description: Creates simple and secure slugs from a given string or random slugs
|
21
|
+
description: Creates simple and secure slugs from a given string or random slugs.
|
17
22
|
email: m@venlix.net
|
18
23
|
executables: []
|
19
24
|
|
@@ -21,18 +26,20 @@ extensions: []
|
|
21
26
|
|
22
27
|
extra_rdoc_files:
|
23
28
|
- README.rdoc
|
24
|
-
- CHANGELOG.rdoc
|
25
29
|
- LICENSE
|
26
30
|
files:
|
27
|
-
- lib/speaking_id/speaking_id.rb
|
28
31
|
- lib/speaking_id.rb
|
32
|
+
- lib/speaking_id/speaking_id.rb
|
33
|
+
- lib/speaking_id/ascii_approximations.rb
|
34
|
+
- lib/speaking_id/core_extensions.rb
|
35
|
+
- rails/init.rb
|
29
36
|
- test/speaking_id_test.rb
|
37
|
+
- test/core_extensions_test.rb
|
38
|
+
- test/schema.rb
|
30
39
|
- test/test_helper.rb
|
31
40
|
- LICENSE
|
32
41
|
- README.rdoc
|
33
42
|
- Rakefile
|
34
|
-
- CHANGELOG.rdoc
|
35
|
-
- init.rb
|
36
43
|
has_rdoc: true
|
37
44
|
homepage: http://github.com/venlix/speaking_id
|
38
45
|
licenses: []
|
@@ -52,20 +59,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
59
|
requirements:
|
53
60
|
- - ">="
|
54
61
|
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
55
64
|
version: "0"
|
56
|
-
version:
|
57
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
66
|
requirements:
|
59
67
|
- - ">="
|
60
68
|
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 1
|
71
|
+
- 2
|
61
72
|
version: "1.2"
|
62
|
-
version:
|
63
73
|
requirements: []
|
64
74
|
|
65
75
|
rubyforge_project:
|
66
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.6
|
67
77
|
signing_key:
|
68
78
|
specification_version: 3
|
69
|
-
summary:
|
79
|
+
summary: Lightweight Ruby on Rails gem to handle slugs for Active Record objects.
|
70
80
|
test_files: []
|
71
81
|
|
data/CHANGELOG.rdoc
DELETED
data/init.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'speaking_id'
|