handler 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,8 @@
1
+ = Changelog
2
+
3
+ Per-release changes to Handler.
4
+
5
+
6
+ == 0.8.0 (2009 Oct 12)
7
+
8
+ First release.
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
+
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
4
+ require 'test/unit'
5
+
6
+ # required to avoid errors
7
+ module ActiveRecord
8
+ class Base; end
9
+ end
10
+ require 'handler'
11
+
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