handler 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/CHANGELOG.rdoc +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +35 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/handler.gemspec +51 -0
- data/init.rb +1 -0
- data/lib/handler.rb +127 -0
- data/test/handler_test.rb +94 -0
- data/test/test_helper.rb +11 -0
- metadata +67 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Alex Reisner
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
= Handler
|
2
|
+
|
3
|
+
Handler is a Rails plugin that generates handles (filesystem- and URL-friendly names) for ActiveRecord models based on a given attribute or method. For example, in your model:
|
4
|
+
|
5
|
+
handle_based_on :title
|
6
|
+
|
7
|
+
creates a generate_handle method which returns a lowercase ASCII version of the title attribute, for example:
|
8
|
+
|
9
|
+
"Häagen Dazs" --> "haagen_dazs"
|
10
|
+
".38 Special" --> "38_special"
|
11
|
+
"Guns N' Roses" --> "guns_n_roses"
|
12
|
+
"Emerson, Lake & Palmer" --> "emerson_lake_and_palmer"
|
13
|
+
|
14
|
+
By default the word separator is "_" but you can change this with the <tt>:separator</tt> option, for example:
|
15
|
+
|
16
|
+
handle_based_on :title, :separator => "-"
|
17
|
+
|
18
|
+
Transliteration using reasonable ASCII approximations of non-ASCII characters is attempted, using the excellent unidecode gem (http://rubyforge.org/projects/unidecode) if it's available (recommended: <tt>sudo gem install unidecode</tt>).
|
19
|
+
|
20
|
+
Usually you want handles to be unique but if they're generated automatically you can't very well use ActiveRecord validation. Instead Handler will append numbers (starting with 2) to duplicate handles, so if you already have a Person with handle <tt>george_brett</tt>, a new Person with name "George Brett" will receive the handle <tt>george_brett_2</tt>. To disable this feature (and allow duplicate handles):
|
21
|
+
|
22
|
+
handle_based_on :name, :unique => false
|
23
|
+
|
24
|
+
Handler doesn't store handles automatically, but provides a private method <tt>assign_handle</tt> you can call to do so. Intended use is something like this:
|
25
|
+
|
26
|
+
before_save :assign_handle
|
27
|
+
|
28
|
+
By default the handle is written to the <tt>handle</tt> attribute but you can change this like so:
|
29
|
+
|
30
|
+
handle_based_on :name, :write_to => :alias
|
31
|
+
|
32
|
+
The above will store the handle as the <tt>alias</tt> attribute.
|
33
|
+
|
34
|
+
|
35
|
+
Copyright (c) 2009 Alex Reisner, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "handler"
|
8
|
+
gem.summary = %Q{Handler generates filesystem- and URL-friendly names for ActiveRecord models.}
|
9
|
+
gem.description = %Q{Handler is a Rails plugin that generates handles (filesystem- and URL-friendly names) for ActiveRecord models based on a given attribute or method.}
|
10
|
+
gem.email = "alex@alexreisner.com"
|
11
|
+
gem.homepage = "http://github.com/alexreisner/handler"
|
12
|
+
gem.authors = ["Alex Reisner"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/*_test.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
if File.exist?('VERSION')
|
47
|
+
version = File.read('VERSION')
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "Handler #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.0
|
data/handler.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{handler}
|
8
|
+
s.version = "0.8.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Alex Reisner"]
|
12
|
+
s.date = %q{2009-10-13}
|
13
|
+
s.description = %q{Handler is a Rails plugin that generates handles (filesystem- and URL-friendly names) for ActiveRecord models based on a given attribute or method.}
|
14
|
+
s.email = %q{alex@alexreisner.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"CHANGELOG.rdoc",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"handler.gemspec",
|
27
|
+
"init.rb",
|
28
|
+
"lib/handler.rb",
|
29
|
+
"test/handler_test.rb",
|
30
|
+
"test/test_helper.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/alexreisner/handler}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.5}
|
36
|
+
s.summary = %q{Handler generates filesystem- and URL-friendly names for ActiveRecord models.}
|
37
|
+
s.test_files = [
|
38
|
+
"test/handler_test.rb",
|
39
|
+
"test/test_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
else
|
48
|
+
end
|
49
|
+
else
|
50
|
+
end
|
51
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'handler'
|
data/lib/handler.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Handler
|
2
|
+
|
3
|
+
##
|
4
|
+
# Included hook: extend including class.
|
5
|
+
#
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Transliterate a string: change accented Unicode characters to ASCII
|
12
|
+
# approximations. Tries several methods, attempting the best first:
|
13
|
+
#
|
14
|
+
# 1. unidecode, if installed (http://rubyforge.org/projects/unidecode)
|
15
|
+
# 2. iconv (included with Ruby, doesn't work with all Ruby versions)
|
16
|
+
# 3. normalize, then remove un-normalizable characters
|
17
|
+
#
|
18
|
+
def self.transliterate(string)
|
19
|
+
transliterate_with_unidecode(string) or
|
20
|
+
transliterate_with_iconv(string) or
|
21
|
+
transliterate_with_normalization(string) or
|
22
|
+
string
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Transliterate a string with the unidecode library.
|
27
|
+
# Return nil if unsuccessful (eg, library not available).
|
28
|
+
#
|
29
|
+
def self.transliterate_with_unidecode(string)
|
30
|
+
begin
|
31
|
+
require 'unidecode'
|
32
|
+
string.to_ascii
|
33
|
+
rescue LoadError
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Transliterate a string with the iconv library.
|
40
|
+
# Return nil if unsuccessful (eg, library not available).
|
41
|
+
#
|
42
|
+
def self.transliterate_with_iconv(string)
|
43
|
+
begin
|
44
|
+
Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s
|
45
|
+
rescue
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Generate a handle from a string.
|
52
|
+
#
|
53
|
+
def self.generate_handle(title, separator)
|
54
|
+
str = title
|
55
|
+
return nil unless str.is_a?(String)
|
56
|
+
str = transliterate(str).to_s
|
57
|
+
str = str.downcase
|
58
|
+
str = str.strip
|
59
|
+
str = str.gsub('&', ' and ') # add space for, e.g., "Y&T"
|
60
|
+
str = str.delete('.\'"') # no space
|
61
|
+
str = str.gsub(/\W/, ' ') # space
|
62
|
+
str = str.gsub(/ +/, separator)
|
63
|
+
str
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Get the next handle in the sequence (for avoiding duplicates).
|
68
|
+
#
|
69
|
+
def self.next_handle(handle, separator)
|
70
|
+
if handle =~ /#{separator}\d+$/
|
71
|
+
handle.sub(/\d+$/){ |i| i.to_i + 1 }
|
72
|
+
else
|
73
|
+
handle + separator + "2"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Transliterate a string using multibyte normalization,
|
79
|
+
# then remove remaining non-ASCII characters. Taken from
|
80
|
+
# <tt>ActiveSupport::Inflector.transliterate</tt>.
|
81
|
+
#
|
82
|
+
def self.transliterate_with_normalization(string)
|
83
|
+
string.mb_chars.normalize.gsub(/[^\x00-\x7F]+/, '').to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
module ClassMethods
|
87
|
+
|
88
|
+
##
|
89
|
+
# Declare that a model generates a handle based on
|
90
|
+
# a given attribute or method. Options include:
|
91
|
+
#
|
92
|
+
# <tt>:separator</tt> - character to place between words
|
93
|
+
# <tt>:store</tt> - attribute in which to store handle
|
94
|
+
# <tt>:unique</tt> - generate a handle which is unique among all records
|
95
|
+
#
|
96
|
+
def handle_based_on(attribute, options = {})
|
97
|
+
options[:separator] ||= "_"
|
98
|
+
options[:write_to] ||= :handle
|
99
|
+
options[:unique] = true if options[:unique].nil?
|
100
|
+
|
101
|
+
##
|
102
|
+
# Generate a URL-friendly name.
|
103
|
+
#
|
104
|
+
define_method :generate_handle do
|
105
|
+
h = Handler.generate_handle(send(attribute), options[:separator])
|
106
|
+
if options[:unique]
|
107
|
+
|
108
|
+
# increase number while *other* records exist with the same handle
|
109
|
+
# (record might be saved and should keep its handle)
|
110
|
+
while self.class.all(:conditions => ["#{options[:write_to]} = ? AND id != ?", h, id]).size > 0
|
111
|
+
h = Handler.next_handle(h, options[:separator])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
h
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Assign the generated handle to the specified attribute.
|
119
|
+
#
|
120
|
+
define_method :assign_handle do
|
121
|
+
write_attribute(options[:write_to], generate_handle)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
ActiveRecord::Base.class_eval{ include Handler }
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class HandlerTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
test "transliteration" do
|
6
|
+
a = Band.new
|
7
|
+
a.name = "Häagen Dazs"
|
8
|
+
possibilities = [
|
9
|
+
"haagen_dazs", # output of unidecode or normalization
|
10
|
+
"hagen_dazs", # output of iconv
|
11
|
+
]
|
12
|
+
assert possibilities.include?(a.generate_handle)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "custom separator" do
|
16
|
+
a = Person.new
|
17
|
+
a.name = "Muddy Waters"
|
18
|
+
assert_equal "muddy-waters", a.generate_handle
|
19
|
+
end
|
20
|
+
|
21
|
+
test "punctuation replacement" do
|
22
|
+
b = Band.new
|
23
|
+
{
|
24
|
+
".38 Special" => "38_special",
|
25
|
+
"Guns N' Roses" => "guns_n_roses",
|
26
|
+
"The Rossington-Collins Band" => "the_rossington_collins_band"
|
27
|
+
|
28
|
+
}.each do |s,t|
|
29
|
+
b.name = s
|
30
|
+
assert_equal t, b.generate_handle
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
test "ampersand replacement" do
|
35
|
+
b = Band.new
|
36
|
+
{
|
37
|
+
"Y&T" => "y_and_t",
|
38
|
+
"Huey Lewis & the News" => "huey_lewis_and_the_news",
|
39
|
+
"Emerson, Lake & Palmer" => "emerson_lake_and_palmer"
|
40
|
+
|
41
|
+
}.each do |s,t|
|
42
|
+
b.name = s
|
43
|
+
assert_equal t, b.generate_handle
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
test "next handle" do
|
48
|
+
assert_equal "banana_boat_2", Handler.next_handle("banana_boat", "_")
|
49
|
+
assert_equal "banana-boat-3", Handler.next_handle("banana-boat-2", "-")
|
50
|
+
assert_equal "banana-boat-12", Handler.next_handle("banana-boat-11", "-")
|
51
|
+
end
|
52
|
+
|
53
|
+
test "unique handle generation" do
|
54
|
+
p = Person.new
|
55
|
+
p.name = "Captain Beefheart"
|
56
|
+
assert_equal "captain-beefheart-2", p.generate_handle
|
57
|
+
p.name = "Abe Lincoln"
|
58
|
+
assert_equal "abe-lincoln-4", p.generate_handle
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ActiveRecordSimulator
|
63
|
+
include Handler
|
64
|
+
attr_accessor :name
|
65
|
+
|
66
|
+
# simulate id method
|
67
|
+
def id; 123; end
|
68
|
+
|
69
|
+
# simulate ActiveRecord::Base.all method;
|
70
|
+
# needed for testing whether records exist with a given handle
|
71
|
+
def self.all(options)
|
72
|
+
@existing_handles.include?(options[:conditions][1]) ? [nil, nil] : []
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Band < ActiveRecordSimulator
|
77
|
+
handle_based_on :name
|
78
|
+
@existing_handles = [
|
79
|
+
"the_residents",
|
80
|
+
"bing_crosby"
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
class Person < ActiveRecordSimulator
|
85
|
+
handle_based_on :name, :separator => "-"
|
86
|
+
@existing_handles = [
|
87
|
+
"van-dyke-parks",
|
88
|
+
"captain-beefheart",
|
89
|
+
"abe-lincoln",
|
90
|
+
"abe-lincoln-2",
|
91
|
+
"abe-lincoln-3"
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: handler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Reisner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-13 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Handler is a Rails plugin that generates handles (filesystem- and URL-friendly names) for ActiveRecord models based on a given attribute or method.
|
17
|
+
email: alex@alexreisner.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- CHANGELOG.rdoc
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- handler.gemspec
|
33
|
+
- init.rb
|
34
|
+
- lib/handler.rb
|
35
|
+
- test/handler_test.rb
|
36
|
+
- test/test_helper.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/alexreisner/handler
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Handler generates filesystem- and URL-friendly names for ActiveRecord models.
|
65
|
+
test_files:
|
66
|
+
- test/handler_test.rb
|
67
|
+
- test/test_helper.rb
|