has_handle_fallback 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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +56 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/has_handle_fallback.rb +106 -0
- data/test/helper.rb +15 -0
- data/test/test_has_handle_fallback.rb +83 -0
- metadata +74 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Seamus Abshere
|
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,56 @@
|
|
1
|
+
= has_handle_fallback
|
2
|
+
|
3
|
+
Make it easy to use handles (callsigns/monikers/usernames) in URLs, even if they might be blank.
|
4
|
+
|
5
|
+
== Quickstart
|
6
|
+
|
7
|
+
class Person < ActiveRecord::Base
|
8
|
+
has_handle_fallback :email
|
9
|
+
end
|
10
|
+
|
11
|
+
This assumes that <tt>Person</tt> has the fields <tt>handle</tt> and <tt>email</tt>.
|
12
|
+
|
13
|
+
class PeopleController < ActionController::Base
|
14
|
+
def object
|
15
|
+
@object ||= Person.find_by_id_or_handle(params[:id])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Then you can safely use <tt>Person#to_param</tt> in URLs, etc. because, when in doubt, the finder will use the numeric ID.
|
20
|
+
|
21
|
+
Long forms: Person.find_by_id_or_handle('pierrebourdieu'), Person.find_by_id_or_handle(1)
|
22
|
+
Short forms: Person['pierrebourdieu'], Person[1]
|
23
|
+
|
24
|
+
== What's going on?
|
25
|
+
|
26
|
+
def test_only_uses_handle_as_param_when_not_changed_from_value_in_database
|
27
|
+
pierre = Person.new :email => 'pierre.bourdieu@example.com'
|
28
|
+
|
29
|
+
# not saved, so to_param is just blank
|
30
|
+
assert_equal '', pierre.to_param
|
31
|
+
assert_equal nil, Person[pierre.to_param]
|
32
|
+
|
33
|
+
# no handle set, so to_param is integer primary key
|
34
|
+
pierre.save!
|
35
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
36
|
+
assert_equal pierre, Person[pierre.to_param]
|
37
|
+
|
38
|
+
# handle is set, but not saved, so STILL use integer primary key
|
39
|
+
pierre.handle = 'pierrebourdieu'
|
40
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
41
|
+
assert_equal pierre, Person[pierre.to_param]
|
42
|
+
|
43
|
+
# now handle is saved, so we can use it as the param
|
44
|
+
pierre.save!
|
45
|
+
assert_equal 'pierrebourdieu', pierre.to_param
|
46
|
+
assert_equal pierre, Person[pierre.to_param]
|
47
|
+
|
48
|
+
# handle was changed, so let's use the integer primary key until it's saved again
|
49
|
+
pierre.handle = ''
|
50
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
51
|
+
assert_equal pierre, Person[pierre.to_param]
|
52
|
+
end
|
53
|
+
|
54
|
+
== Copyright
|
55
|
+
|
56
|
+
Copyright (c) 2010 Seamus Abshere. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "has_handle_fallback"
|
8
|
+
gem.summary = %Q{Make it easy to use handles (callsigns/monikers/usernames) in URLs, even if they might be blank.}
|
9
|
+
gem.description = %Q{Make it easy to use handles (callsigns/monikers/usernames) in URLs, even if they might be blank.}
|
10
|
+
gem.email = "seamus@abshere.net"
|
11
|
+
gem.homepage = "http://github.com/seamusabshere/has_handle_fallback"
|
12
|
+
gem.authors = ["Seamus Abshere"]
|
13
|
+
gem.add_dependency 'activerecord', '~>2.3.4'
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "has_handle_fallback #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module HasHandleFallback
|
2
|
+
SUB_REGEXP = '\_\-a-zA-Z0-9'
|
3
|
+
REGEXP = /\A[#{SUB_REGEXP}]+\z/
|
4
|
+
ANTI_REGEXP = /[^#{SUB_REGEXP}]+/
|
5
|
+
LENGTH_RANGE = 2..32
|
6
|
+
RECORD_ID_REGEXP = /\A\d+\z/
|
7
|
+
|
8
|
+
def self.str2handle(str)
|
9
|
+
str = str.gsub ANTI_REGEXP, ''
|
10
|
+
str << ('_' * LENGTH_RANGE.min - str.length) unless LENGTH_RANGE.include?(str.length)
|
11
|
+
str
|
12
|
+
end
|
13
|
+
|
14
|
+
module ActiveRecordBaseMethods
|
15
|
+
def has_handle_fallback(fallback_column, options = {})
|
16
|
+
include InstanceMethods
|
17
|
+
extend ClassMethods
|
18
|
+
|
19
|
+
class_eval do
|
20
|
+
cattr_accessor :has_handle_fallback_options
|
21
|
+
self.has_handle_fallback_options = {}
|
22
|
+
has_handle_fallback_options[:fallback_column] = fallback_column.to_s
|
23
|
+
has_handle_fallback_options[:handle_column] = options.delete(:handle_column) || 'handle'
|
24
|
+
|
25
|
+
validate :handle_is_valid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def find_by_id_or_handle(param)
|
32
|
+
return if param.blank?
|
33
|
+
param = param.to_s
|
34
|
+
if param.to_s =~ HasHandleFallback::RECORD_ID_REGEXP
|
35
|
+
find_by_id param
|
36
|
+
else
|
37
|
+
send "find_by_#{has_handle_fallback_options[:handle_column]}", param
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias :[] :find_by_id_or_handle
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
def handle_is_valid
|
45
|
+
raw = read_attribute self.class.has_handle_fallback_options[:handle_column]
|
46
|
+
cooked = handle_fallback
|
47
|
+
|
48
|
+
# inline check to make sure the handle_fallback method works
|
49
|
+
unless cooked =~ HasHandleFallback::REGEXP and HasHandleFallback::LENGTH_RANGE.include?(cooked.length)
|
50
|
+
raise "Dear Developer: your handle_fallback method is not generating valid handles (generated '#{handle_fallback}' for '#{raw}')"
|
51
|
+
end
|
52
|
+
|
53
|
+
# allow nils but not blanks
|
54
|
+
if !raw.nil? and raw.blank?
|
55
|
+
errors.add self.class.has_handle_fallback_options[:handle_column], "can't be blank if you're presented the opportunity to set it"
|
56
|
+
end
|
57
|
+
|
58
|
+
# trapdoor for nil handles
|
59
|
+
return if raw.nil?
|
60
|
+
|
61
|
+
# don't allow all integer handles, because it looks like a database record id
|
62
|
+
if raw =~ HasHandleFallback::RECORD_ID_REGEXP
|
63
|
+
errors.add self.class.has_handle_fallback_options[:handle_column], "can't be entirely composed of integers"
|
64
|
+
end
|
65
|
+
|
66
|
+
# validates_format_of :handle, :with => HasHandleFallback::REGEXP, :allow_nil => true
|
67
|
+
unless raw =~ HasHandleFallback::REGEXP
|
68
|
+
errors.add self.class.has_handle_fallback_options[:handle_column], "contains invalid characters"
|
69
|
+
end
|
70
|
+
|
71
|
+
# validates_length_of :handle, :in => HasHandleFallback::LENGTH_RANGE, :allow_nil => true
|
72
|
+
unless HasHandleFallback::LENGTH_RANGE.include? raw.length
|
73
|
+
errors.add self.class.has_handle_fallback_options[:handle_column], "must be #{HasHandleFallback::LENGTH_RANGE} characters in length"
|
74
|
+
end
|
75
|
+
|
76
|
+
# validates_uniqueness_of :handle, :case_sensitive => false, :allow_nil => true
|
77
|
+
if self.class.exists? [ "LOWER(#{self.class.quoted_table_name}.`#{self.class.has_handle_fallback_options[:handle_column]}`) = ?", raw.downcase ]
|
78
|
+
errors.add self.class.has_handle_fallback_options[:handle_column], "isn't unique"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle
|
83
|
+
raw = read_attribute self.class.has_handle_fallback_options[:handle_column]
|
84
|
+
raw.present? ? raw : handle_fallback
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_fallback
|
88
|
+
fallback = read_attribute self.class.has_handle_fallback_options[:fallback_column]
|
89
|
+
fallback = fallback.split('@').first if fallback.include? '@'
|
90
|
+
HasHandleFallback.str2handle fallback
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_param
|
94
|
+
raw = read_attribute self.class.has_handle_fallback_options[:handle_column]
|
95
|
+
if new_record?
|
96
|
+
''
|
97
|
+
elsif raw.blank? or changes.include?(self.class.has_handle_fallback_options[:handle_column])
|
98
|
+
id.to_s
|
99
|
+
else
|
100
|
+
raw
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
ActiveRecord::Base.extend HasHandleFallback::ActiveRecordBaseMethods
|
data/test/helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
require 'has_handle_fallback'
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(
|
13
|
+
'adapter' => 'sqlite3',
|
14
|
+
'database' => 'test/test.sqlite3'
|
15
|
+
)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
ActiveRecord::Schema.define(:version => 20090819143429) do
|
4
|
+
create_table 'people', :force => true do |t|
|
5
|
+
t.string :email
|
6
|
+
t.string :handle
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table 'cats', :force => true do |t|
|
10
|
+
t.string :name
|
11
|
+
t.string :moniker
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person < ActiveRecord::Base
|
16
|
+
has_handle_fallback :email
|
17
|
+
end
|
18
|
+
|
19
|
+
class Cat < ActiveRecord::Base
|
20
|
+
has_handle_fallback :name, :handle_column => 'moniker'
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestHasHandleFallback < Test::Unit::TestCase
|
24
|
+
def test_has_handle
|
25
|
+
ab = Person.new :email => 'a.b@example.com', :handle => 'AB'
|
26
|
+
assert_equal 'AB', ab.handle
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_has_fallback_handle_based_on_email
|
30
|
+
ab = Person.new :email => 'a.b@example.com'
|
31
|
+
assert_equal 'ab', ab.handle
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_can_use_alternate_columns
|
35
|
+
pierre = Cat.new :name => 'Pierre Bourdieu'
|
36
|
+
assert_equal 'PierreBourdieu', pierre.handle
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_has_validations
|
40
|
+
assert_equal true, Person.new(:email => 'pierre.bourdieu@example.com', :handle => 'Pierre-Bourdieu_99').valid?
|
41
|
+
assert_equal false, Person.new(:email => 'pierre.bourdieu@example.com', :handle => 'Pierre:Bourdieu_99').valid?
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_can_have_nil_handle
|
45
|
+
assert_equal true, Person.new(:email => 'pierre.bourdieu@example.com', :handle => nil).valid?
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_cannot_have_blank_handle
|
49
|
+
assert_equal false, Person.new(:email => 'pierre.bourdieu@example.com', :handle => ' ').valid?
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_is_careful_with_things_that_look_like_emails
|
53
|
+
assert_equal 'pierrebourdieu', Person.new(:email => 'pierre.bourdieu@example.com').handle
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_only_uses_handle_as_param_when_not_changed_from_value_in_database
|
57
|
+
pierre = Person.new(:email => 'pierre.bourdieu@example.com')
|
58
|
+
|
59
|
+
# not saved, so to_param is just blank
|
60
|
+
assert_equal '', pierre.to_param
|
61
|
+
assert_equal nil, Person[pierre.to_param]
|
62
|
+
|
63
|
+
# no handle set, so to_param is integer primary key
|
64
|
+
pierre.save!
|
65
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
66
|
+
assert_equal pierre, Person[pierre.to_param]
|
67
|
+
|
68
|
+
# handle is set, but not saved, so STILL use integer primary key
|
69
|
+
pierre.handle = 'pierrebourdieu'
|
70
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
71
|
+
assert_equal pierre, Person[pierre.to_param]
|
72
|
+
|
73
|
+
# now handle is saved, so we can use it as the param
|
74
|
+
pierre.save!
|
75
|
+
assert_equal 'pierrebourdieu', pierre.to_param
|
76
|
+
assert_equal pierre, Person[pierre.to_param]
|
77
|
+
|
78
|
+
# handle was changed, so let's use the integer primary key until it's saved again
|
79
|
+
pierre.handle = ''
|
80
|
+
assert_equal pierre.id.to_s, pierre.to_param
|
81
|
+
assert_equal pierre, Person[pierre.to_param]
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_handle_fallback
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Seamus Abshere
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-03-15 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.4
|
24
|
+
version:
|
25
|
+
description: Make it easy to use handles (callsigns/monikers/usernames) in URLs, even if they might be blank.
|
26
|
+
email: seamus@abshere.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- lib/has_handle_fallback.rb
|
42
|
+
- test/helper.rb
|
43
|
+
- test/test_has_handle_fallback.rb
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/seamusabshere/has_handle_fallback
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Make it easy to use handles (callsigns/monikers/usernames) in URLs, even if they might be blank.
|
72
|
+
test_files:
|
73
|
+
- test/helper.rb
|
74
|
+
- test/test_has_handle_fallback.rb
|