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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ test/test.sqlite3
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