pluginaweek-has_phone_numbers 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/CHANGELOG.rdoc +37 -0
- data/LICENSE +20 -0
- data/README.rdoc +71 -0
- data/Rakefile +96 -0
- data/app/models/phone_number.rb +131 -0
- data/db/migrate/001_create_phone_numbers.rb +15 -0
- data/init.rb +1 -0
- data/lib/has_phone_numbers.rb +14 -0
- data/test/app_root/app/models/person.rb +3 -0
- data/test/app_root/db/migrate/001_create_people.rb +11 -0
- data/test/app_root/db/migrate/002_migrate_has_phone_numbers_to_version_1.rb +13 -0
- data/test/factory.rb +49 -0
- data/test/functional/has_phone_numbers_test.rb +22 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/phone_number_test.rb +319 -0
- metadata +77 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
== master
|
2
|
+
|
3
|
+
== 0.2.0 / 2009-04-19
|
4
|
+
|
5
|
+
* Add the ability to parse raw values [Matt Lightner]
|
6
|
+
* Validate that the country code is known
|
7
|
+
* Validate number lengths on a per-country code basis
|
8
|
+
* Add the list of available country codes
|
9
|
+
* Remove PhoneNumber#display_value in favor of using the Rails helper
|
10
|
+
* Add dependency on Rails 2.3
|
11
|
+
* Remove dependency on plugins_plus
|
12
|
+
|
13
|
+
== 0.1.0 / 2008-12-14
|
14
|
+
|
15
|
+
* Remove the PluginAWeek namespace
|
16
|
+
|
17
|
+
== 0.0.5 / 2008-10-26
|
18
|
+
|
19
|
+
* Change how the base module is included to prevent namespacing conflicts
|
20
|
+
* Change PhoneNumber#to_s to PhoneNumber#display_value
|
21
|
+
|
22
|
+
== 0.0.4 / 2008-06-22
|
23
|
+
|
24
|
+
* Remove log files from gems
|
25
|
+
|
26
|
+
== 0.0.3 / 2008-05-05
|
27
|
+
|
28
|
+
* Updated documentation
|
29
|
+
|
30
|
+
== 0.0.2 / 2007-09-26
|
31
|
+
|
32
|
+
* Move test fixtures out of the test application root directory
|
33
|
+
|
34
|
+
== 0.0.1 / 2007-08-21
|
35
|
+
|
36
|
+
* Added documentation
|
37
|
+
* Removed dependency on has_association_helper
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006-2009 Aaron Pfeifer
|
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,71 @@
|
|
1
|
+
= has_phone_numbers
|
2
|
+
|
3
|
+
+has_phone_numbers+ demonstrates a reference implementation for handling phone numbers.
|
4
|
+
|
5
|
+
== Resources
|
6
|
+
|
7
|
+
API
|
8
|
+
|
9
|
+
* http://api.pluginaweek.org/has_phone_numbers
|
10
|
+
|
11
|
+
Bugs
|
12
|
+
|
13
|
+
* http://pluginaweek.lighthouseapp.com/projects/13275-has_phone_numbers
|
14
|
+
|
15
|
+
Development
|
16
|
+
|
17
|
+
* http://github.com/pluginaweek/has_phone_numbers
|
18
|
+
|
19
|
+
Source
|
20
|
+
|
21
|
+
* git://github.com/pluginaweek/has_phone_numbers.git
|
22
|
+
|
23
|
+
== Description
|
24
|
+
|
25
|
+
A phone number is a simple model whose data and functionality should be
|
26
|
+
standardized across multiple applications. Phone numbers are minimalistic in
|
27
|
+
terms of the type of data it represents. Both U.S. and international formats
|
28
|
+
are supported.
|
29
|
+
|
30
|
+
== Usage
|
31
|
+
|
32
|
+
Note that this is a reference implementation and, most likely, will be
|
33
|
+
modified for your own usage.
|
34
|
+
|
35
|
+
=== Example
|
36
|
+
|
37
|
+
Building with individual segments:
|
38
|
+
|
39
|
+
PhoneNumber.create(:phoneable => user, :country_code => '1', :number => '1234567890', :extension => '123')
|
40
|
+
# #<PhoneNumber id: 1, phoneable_id: 1, phoneable_type: "User", country_code: "1", number: "1234567890", extension: "123", created_at: "2009-01-01 00:00:00", updated_at: "2009-01-01 00:00:00">
|
41
|
+
|
42
|
+
Parsing raw values:
|
43
|
+
|
44
|
+
PhoneNumber.create(:phoneable => user, :content => '1 1234567890 ext. 123')
|
45
|
+
# #<PhoneNumber id: 1, phoneable_id: 1, phoneable_type: "User", country_code: "1", number: "1234567890", extension: "123", created_at: "2009-01-01 00:00:00", updated_at: "2009-01-01 00:00:00">
|
46
|
+
|
47
|
+
PhoneNumber.create(:phoneable => user, :content => '231 331 996 x4621')
|
48
|
+
# #<PhoneNumber id: 1, phoneable_id: 1, phoneable_type: "User", country_code: "231", number: "331996", extension: "4621", created_at: "2009-01-01 00:00:00", updated_at: "2009-01-01 00:00:00">
|
49
|
+
|
50
|
+
PhoneNumber.create(:phoneable => user, :content => '+ 386 1 5853 449')
|
51
|
+
# #<PhoneNumber id: 1, phoneable_id: 1, phoneable_type: "User", country_code: "386", number: "15853449", extension: nil, created_at: "2009-01-01 00:00:00", updated_at: "2009-01-01 00:00:00">
|
52
|
+
|
53
|
+
PhoneNumber.create(:phoneable => user, :content => '+39-02-4823001')
|
54
|
+
# #<PhoneNumber id: 1, phoneable_id: 1, phoneable_type: "User", country_code: "39", number: "024823001", extension: nil, created_at: "2009-01-01 00:00:00", updated_at: "2009-01-01 00:00:00">
|
55
|
+
|
56
|
+
== Testing
|
57
|
+
|
58
|
+
Before you can run any tests, the following gem must be installed:
|
59
|
+
* plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
|
60
|
+
|
61
|
+
To run against a specific version of Rails:
|
62
|
+
|
63
|
+
rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
|
64
|
+
|
65
|
+
== Dependencies
|
66
|
+
|
67
|
+
* Rails 2.3 or later
|
68
|
+
|
69
|
+
== References
|
70
|
+
|
71
|
+
* Casey West - {Parse-PhoneNumber}[http://search.cpan.org/~cwest/Parse-PhoneNumber-1.1]
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/sshpublisher'
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = 'has_phone_numbers'
|
8
|
+
s.version = '0.2.0'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'Demonstrates a reference implementation for handling phone numbers in ActiveRecord'
|
11
|
+
s.description = s.summary
|
12
|
+
|
13
|
+
s.files = FileList['{app,db,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
14
|
+
s.require_path = 'lib'
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.test_files = Dir['test/**/*_test.rb']
|
17
|
+
|
18
|
+
s.author = 'Aaron Pfeifer'
|
19
|
+
s.email = 'aaron@pluginaweek.org'
|
20
|
+
s.homepage = 'http://www.pluginaweek.org'
|
21
|
+
s.rubyforge_project = 'pluginaweek'
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Default: run all tests.'
|
25
|
+
task :default => :test
|
26
|
+
|
27
|
+
desc "Test the #{spec.name} plugin."
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
29
|
+
t.libs << 'lib'
|
30
|
+
t.test_files = spec.test_files
|
31
|
+
t.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
namespace :test do
|
37
|
+
desc "Test the #{spec.name} plugin with Rcov."
|
38
|
+
Rcov::RcovTask.new(:rcov) do |t|
|
39
|
+
t.libs << 'lib'
|
40
|
+
t.test_files = spec.test_files
|
41
|
+
t.rcov_opts << '--exclude="^(?!lib/|app/)"'
|
42
|
+
t.verbose = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue LoadError
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Generate documentation for the #{spec.name} plugin."
|
49
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = spec.name
|
52
|
+
rdoc.template = '../rdoc_template.rb'
|
53
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
54
|
+
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Generate a gemspec file.'
|
58
|
+
task :gemspec do
|
59
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
60
|
+
f.write spec.to_ruby
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Rake::GemPackageTask.new(spec) do |p|
|
65
|
+
p.gem_spec = spec
|
66
|
+
p.need_tar = true
|
67
|
+
p.need_zip = true
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'Publish the beta gem.'
|
71
|
+
task :pgem => [:package] do
|
72
|
+
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Publish the API documentation.'
|
76
|
+
task :pdoc => [:rdoc] do
|
77
|
+
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'Publish the API docs and gem'
|
81
|
+
task :publish => [:pgem, :pdoc, :release]
|
82
|
+
|
83
|
+
desc 'Publish the release files to RubyForge.'
|
84
|
+
task :release => [:gem, :package] do
|
85
|
+
require 'rubyforge'
|
86
|
+
|
87
|
+
ruby_forge = RubyForge.new.configure
|
88
|
+
ruby_forge.login
|
89
|
+
|
90
|
+
%w(gem tgz zip).each do |ext|
|
91
|
+
file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
|
92
|
+
puts "Releasing #{File.basename(file)}..."
|
93
|
+
|
94
|
+
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Represents a phone number split into multiple parts:
|
2
|
+
# * +country_code+ - Uniquely identifiers the country to which the number belongs.
|
3
|
+
# This value is based on the E.164 standard (http://en.wikipedia.org/wiki/E.164)
|
4
|
+
# * +number+ - The subscriber number (10 digits in length)
|
5
|
+
# * +extension+ - A number that can route to different phones at a location
|
6
|
+
#
|
7
|
+
# This phone number format is biased towards those types found in the United
|
8
|
+
# States and may need to be adjusted for international support.
|
9
|
+
class PhoneNumber < ActiveRecord::Base
|
10
|
+
# The valid lengths allowed per country code for area code + subscriber number
|
11
|
+
VALID_LENGTHS = {
|
12
|
+
1 => 10, 7 => 10, 20 => 9, 27 => 9, 30 => 10, 31 => 9,
|
13
|
+
32 => 8..9, 33 => 9..10, 34 => 9, 36 => 8..9, 39 => 9, 40 => 9,
|
14
|
+
41 => 9..10, 43 => 7..13, 44 => 10..11, 45 => 8, 46 => 9, 47 => 8,
|
15
|
+
48 => 9, 49 => 7..11, 51 => 8..9, 52 => 8..10, 53 => 8, 54 => 8..9,
|
16
|
+
55 => 10, 56 => 8..9, 57 => 9..10, 58 => 10, 60 => 9..10, 61 => 9,
|
17
|
+
62 => 8..11, 63 => 8..10, 64 => 8..9, 65 => 8..9, 66 => 9..10, 81 => 9..10,
|
18
|
+
82 => 8..9, 84 => 7..10, 86 => 7..11, 90 => 10, 91 => 10, 92 => 9..10,
|
19
|
+
93 => 8..9, 94 => 10, 95 => 7..8, 98 => 10, 212 => 8, 213 => 8,
|
20
|
+
216 => 8, 218 => 8..9, 220 => 7, 221 => 7, 222 => 7, 223 => 8,
|
21
|
+
224 => 8, 225 => 8, 226 => 8, 227 => 8, 228 => 7, 229 => 8,
|
22
|
+
230 => 7, 231 => 6..8, 232 => 8, 233 => 5..8, 234 => 7..8, 235 => 7,
|
23
|
+
236 => 8, 237 => 8, 238 => 7, 239 => 6..7, 240 => 6, 241 => 6..8,
|
24
|
+
242 => 7, 243 => 8, 244 => 9, 245 => 7, 246 => 4..7, 247 => 4,
|
25
|
+
248 => 6, 249 => 9, 250 => 5..8, 251 => 9, 252 => 7..8, 253 => 6,
|
26
|
+
254 => 9, 255 => 9, 256 => 9, 257 => 8, 258 => 8..9, 260 => 9,
|
27
|
+
261 => 9, 262 => 10, 263 => 8..11, 264 => 6..7, 265 => 8, 266 => 8,
|
28
|
+
267 => 7, 268 => 7, 269 => 7, 290 => 4, 291 => 7, 297 => 7,
|
29
|
+
298 => 6, 299 => 6, 350 => 8, 351 => 9, 352 => 9, 353 => 9,
|
30
|
+
354 => 7..9, 355 => 7..9, 356 => 8, 357 => 8, 358 => 9, 359 => 8..10,
|
31
|
+
370 => 8, 371 => 8, 372 => 7..8, 373 => 8, 374 => 8, 375 => 9,
|
32
|
+
376 => 6..9, 377 => 8..9, 378 => 9..12, 380 => 8..9, 381 => 9, 382 => 8,
|
33
|
+
385 => 8, 386 => 8, 387 => 8, 388 => 8..10, 389 => 7..8, 420 => 9,
|
34
|
+
421 => 9, 423 => 7, 500 => 5, 501 => 7, 502 => 8, 503 => 8,
|
35
|
+
504 => 7..8, 505 => 8, 506 => 8, 507 => 7, 508 => 6, 509 => 8,
|
36
|
+
590 => 10, 591 => 8, 592 => 6..7, 593 => 8..9, 594 => 10, 595 => 9,
|
37
|
+
596 => 10, 597 => 6, 598 => 7..8, 599 => 7, 670 => 7, 672 => 6,
|
38
|
+
673 => 7, 674 => 7, 675 => 7, 676 => 5..7, 677 => 5, 678 => 5..7,
|
39
|
+
679 => 7, 680 => 7, 681 => 6, 682 => 5, 683 => 4, 685 => 6..7,
|
40
|
+
686 => 5, 687 => 6, 688 => 5, 689 => 6, 690 => 4, 691 => 7,
|
41
|
+
692 => 7, 800 => 8..12, 808 => 8, 850 => 8..9, 852 => 8, 853 => 8,
|
42
|
+
855 => 8, 856 => 8, 870 => 9, 871 => 9, 872 => 9, 873 => 9,
|
43
|
+
874 => 9, 878 => 9, 880 => 10, 881 => 6, 882 => 6, 883 => 6,
|
44
|
+
886 => 7..8, 960 => 7, 961 => 8, 962 => 8..9, 963 => 7..8, 964 => 8..10,
|
45
|
+
965 => 7, 966 => 8..9, 967 => 7..9, 968 => 8, 970 => 8, 971 => 7..9,
|
46
|
+
972 => 7..9, 973 => 8, 974 => 7, 975 => 7..8, 976 => 7..10, 977 => 7..8,
|
47
|
+
979 => 9, 991 => 9, 992 => 9, 993 => 8, 994 => 8..9, 995 => 9,
|
48
|
+
996 => 9, 998 => 9
|
49
|
+
}.stringify_keys
|
50
|
+
|
51
|
+
# The list of country calling codes as defined by ITU-T recommendation E.164
|
52
|
+
COUNTRY_CODES = VALID_LENGTHS.keys
|
53
|
+
|
54
|
+
# Whether to always use the default country code configured for this model
|
55
|
+
# when parsing the raw content of a phone number
|
56
|
+
cattr_accessor :use_default_country_code_on_parse
|
57
|
+
@@use_default_country_code_on_parse = false
|
58
|
+
|
59
|
+
belongs_to :phoneable, :polymorphic => true
|
60
|
+
|
61
|
+
validates_presence_of :phoneable_id, :phoneable_type, :country_code, :number
|
62
|
+
validates_numericality_of :number
|
63
|
+
validates_numericality_of :extension, :allow_nil => true
|
64
|
+
validates_length_of :extension, :maximum => 10, :allow_nil => true
|
65
|
+
validates_inclusion_of :country_code, :in => COUNTRY_CODES
|
66
|
+
validates_each :number do |phone_number, attr, value|
|
67
|
+
country_code = phone_number.country_code
|
68
|
+
|
69
|
+
if country_code && length = VALID_LENGTHS[country_code]
|
70
|
+
if length.is_a?(Range)
|
71
|
+
if value.nil? || value.size < length.begin
|
72
|
+
phone_number.errors.add(attr, :too_short, :count => length.begin)
|
73
|
+
elsif value.size > length.end
|
74
|
+
phone_number.errors.add(attr, :too_long, :count => length.end)
|
75
|
+
end
|
76
|
+
elsif value.nil? || value.size != length
|
77
|
+
phone_number.errors.add(attr, :wrong_length, :count => length)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# The raw, unparsed content containing the phone number. This can be parsed
|
83
|
+
# in various formats, such as:
|
84
|
+
# * 599 600 11 22
|
85
|
+
# * + 386 1 5853 449
|
86
|
+
# * +48 (22) 641 0001
|
87
|
+
# * 36 1 267-4636
|
88
|
+
# * +39-02-4823001
|
89
|
+
# * 231 331 996 x4621
|
90
|
+
# * 358 2 141 540 65 ext. 1423
|
91
|
+
attr_accessor :content
|
92
|
+
before_validation_on_create :parse_content, :if => :content
|
93
|
+
|
94
|
+
private
|
95
|
+
# Parses the raw content of a phone number, extracting the following
|
96
|
+
# attributes:
|
97
|
+
# * country_code
|
98
|
+
# * number
|
99
|
+
# * extension
|
100
|
+
def parse_content
|
101
|
+
content = self.content.strip
|
102
|
+
|
103
|
+
# Check for extension
|
104
|
+
if match = content.match(/\s*(?:(?:ext|ex|xt|x)[\s.:]*(\d+))$/i)
|
105
|
+
self.extension = match[1]
|
106
|
+
content.gsub!(match.to_s, '')
|
107
|
+
end
|
108
|
+
|
109
|
+
# Remove non-digits and leading 0
|
110
|
+
content.gsub!(/\D/, '')
|
111
|
+
content.gsub!(/^0+/, '')
|
112
|
+
|
113
|
+
if use_default_country_code_on_parse
|
114
|
+
# Scrub pre-determined country code
|
115
|
+
content.gsub!(/^#{country_code}/, '')
|
116
|
+
else
|
117
|
+
# Try to figure out the country code. It is not possible for one
|
118
|
+
# country code's number to overlap another.
|
119
|
+
(1..3).each do |length|
|
120
|
+
code = content[0, length]
|
121
|
+
if VALID_LENGTHS[code] # Fast lookup
|
122
|
+
self.country_code = code
|
123
|
+
content.gsub!(/^#{code}/, '')
|
124
|
+
break
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
self.number = content
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreatePhoneNumbers < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :phone_numbers do |t|
|
4
|
+
t.references :phoneable, :polymorphic => true, :null => false
|
5
|
+
t.string :country_code, :null => false, :limit => 3, :default => 1 # Default is the United States
|
6
|
+
t.string :number, :null => false, :limit => 12
|
7
|
+
t.string :extension, :limit => 10
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :phone_numbers
|
14
|
+
end
|
15
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'has_phone_numbers'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Adds a generic implementation for dealing with phone numbers
|
2
|
+
module HasPhoneNumbers
|
3
|
+
module MacroMethods
|
4
|
+
# Creates the following association:
|
5
|
+
# * +phone_number+ - All phone numbers associated with the current record.
|
6
|
+
def has_phone_numbers
|
7
|
+
has_many :phone_numbers, :as => :phoneable
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveRecord::Base.class_eval do
|
13
|
+
extend HasPhoneNumbers::MacroMethods
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class MigrateHasPhoneNumbersToVersion1 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
|
4
|
+
migration.migrate(:up)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down
|
9
|
+
ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
|
10
|
+
migration.migrate(:down)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/test/factory.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Factory
|
2
|
+
# Build actions for the model
|
3
|
+
def self.build(model, &block)
|
4
|
+
name = model.to_s.underscore
|
5
|
+
|
6
|
+
define_method("#{name}_attributes", block)
|
7
|
+
define_method("valid_#{name}_attributes") {|*args| valid_attributes_for(model, *args)}
|
8
|
+
define_method("new_#{name}") {|*args| new_record(model, *args)}
|
9
|
+
define_method("create_#{name}") {|*args| create_record(model, *args)}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get valid attributes for the model
|
13
|
+
def valid_attributes_for(model, attributes = {})
|
14
|
+
name = model.to_s.underscore
|
15
|
+
send("#{name}_attributes", attributes)
|
16
|
+
attributes.stringify_keys!
|
17
|
+
attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
# Build an unsaved record
|
21
|
+
def new_record(model, *args)
|
22
|
+
attributes = valid_attributes_for(model, *args)
|
23
|
+
record = model.new(attributes)
|
24
|
+
attributes.each {|attr, value| record.send("#{attr}=", value) if model.accessible_attributes && !model.accessible_attributes.include?(attr) || model.protected_attributes && model.protected_attributes.include?(attr)}
|
25
|
+
record
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build and save/reload a record
|
29
|
+
def create_record(model, *args)
|
30
|
+
record = new_record(model, *args)
|
31
|
+
record.save!
|
32
|
+
record.reload
|
33
|
+
record
|
34
|
+
end
|
35
|
+
|
36
|
+
build Person do |attributes|
|
37
|
+
attributes.reverse_merge!(
|
38
|
+
:name => 'John Doe'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
build PhoneNumber do |attributes|
|
43
|
+
attributes[:phoneable] = create_person unless attributes.include?(:phoneable)
|
44
|
+
attributes.reverse_merge!(
|
45
|
+
:country_code => '1',
|
46
|
+
:number => '1234567890'
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class PersonByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@person = create_person
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_should_not_have_any_phone_numbers
|
9
|
+
assert @person.phone_numbers.empty?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class PersonWithPhoneNumbersTest < Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
@person = create_person
|
16
|
+
@phone_number = create_phone_number(:phoneable => @person)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_have_phone_numbers
|
20
|
+
assert_equal [@phone_number], @person.phone_numbers
|
21
|
+
end
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Load the plugin testing framework
|
2
|
+
$:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
|
3
|
+
require 'rubygems'
|
4
|
+
require 'plugin_test_helper'
|
5
|
+
|
6
|
+
# Run the migrations
|
7
|
+
ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
|
8
|
+
|
9
|
+
# Mixin the factory helper
|
10
|
+
require File.expand_path("#{File.dirname(__FILE__)}/factory")
|
11
|
+
Test::Unit::TestCase.class_eval do
|
12
|
+
include Factory
|
13
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class PhoneNumberByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@phone_number = PhoneNumber.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_should_not_have_a_phoneable_id
|
9
|
+
assert_nil @phone_number.phoneable_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_not_have_a_phoneable_type
|
13
|
+
assert @phone_number.phoneable_type.blank?
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_a_united_states_country_code
|
17
|
+
assert_equal '1', @phone_number.country_code
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_not_have_a_number
|
21
|
+
assert @phone_number.number.blank?
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_not_have_an_extension
|
25
|
+
assert @phone_number.extension.blank?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class PhoneNumberTest < Test::Unit::TestCase
|
30
|
+
def test_should_be_valid_with_a_set_of_valid_attributes
|
31
|
+
phone_number = new_phone_number
|
32
|
+
assert phone_number.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_should_require_a_phoneable_id
|
36
|
+
phone_number = new_phone_number(:phoneable => nil, :phoneable_id => nil)
|
37
|
+
assert !phone_number.valid?
|
38
|
+
assert phone_number.errors.invalid?(:phoneable_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_should_require_a_phoneable_type
|
42
|
+
phone_number = new_phone_number(:phoneable => nil, :phoneable_type => nil)
|
43
|
+
assert !phone_number.valid?
|
44
|
+
assert phone_number.errors.invalid?(:phoneable_type)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_should_require_a_country_code
|
48
|
+
phone_number = new_phone_number(:country_code => nil)
|
49
|
+
assert !phone_number.valid?
|
50
|
+
assert phone_number.errors.invalid?(:country_code)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_should_require_a_known_country_code
|
54
|
+
phone_number = new_phone_number(:country_code => '2')
|
55
|
+
assert !phone_number.valid?
|
56
|
+
assert phone_number.errors.invalid?(:country_code)
|
57
|
+
|
58
|
+
phone_number.country_code = '1'
|
59
|
+
assert phone_number.valid?
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_should_require_a_number
|
63
|
+
phone_number = new_phone_number(:number => nil)
|
64
|
+
assert !phone_number.valid?
|
65
|
+
assert phone_number.errors.invalid?(:number)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_require_number_be_a_number
|
69
|
+
phone_number = new_phone_number(:number => 'a' * 10)
|
70
|
+
assert !phone_number.valid?
|
71
|
+
assert phone_number.errors.invalid?(:number)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_should_require_number_be_exact_for_country_code_without_range
|
75
|
+
phone_number = new_phone_number(:country_code => '1', :number => '1' * 9)
|
76
|
+
assert !phone_number.valid?
|
77
|
+
assert_equal 'is the wrong length (should be 10 characters)', phone_number.errors.on(:number)
|
78
|
+
|
79
|
+
phone_number.number += '1'
|
80
|
+
assert phone_number.valid?
|
81
|
+
|
82
|
+
phone_number.number += '1'
|
83
|
+
assert !phone_number.valid?
|
84
|
+
assert_equal 'is the wrong length (should be 10 characters)', phone_number.errors.on(:number)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_should_require_number_be_within_range_for_country_code_with_range
|
88
|
+
phone_number = new_phone_number(:country_code => '32', :number => '1' * 7)
|
89
|
+
assert !phone_number.valid?
|
90
|
+
assert_equal 'is too short (minimum is 8 characters)', phone_number.errors.on(:number)
|
91
|
+
|
92
|
+
phone_number.number += '1'
|
93
|
+
assert phone_number.valid?
|
94
|
+
|
95
|
+
phone_number.number += '1'
|
96
|
+
assert phone_number.valid?
|
97
|
+
|
98
|
+
phone_number.number += '1'
|
99
|
+
assert !phone_number.valid?
|
100
|
+
assert_equal 'is too long (maximum is 9 characters)', phone_number.errors.on(:number)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_should_not_require_an_extension
|
104
|
+
phone_number = new_phone_number(:extension => nil)
|
105
|
+
assert phone_number.valid?
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_should_require_extension_be_at_most_10_digits
|
109
|
+
phone_number = new_phone_number(:extension => '1' * 9)
|
110
|
+
assert phone_number.valid?
|
111
|
+
|
112
|
+
phone_number.extension += '1'
|
113
|
+
assert phone_number.valid?
|
114
|
+
|
115
|
+
phone_number.extension += '1'
|
116
|
+
assert !phone_number.valid?
|
117
|
+
assert phone_number.errors.invalid?(:extension)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_should_protect_attributes_from_mass_assignment
|
121
|
+
phone_number = PhoneNumber.new(
|
122
|
+
:id => 1,
|
123
|
+
:phoneable_id => 1,
|
124
|
+
:phoneable_type => 'User',
|
125
|
+
:country_code => '123',
|
126
|
+
:number => '8675309',
|
127
|
+
:extension => '999'
|
128
|
+
)
|
129
|
+
|
130
|
+
assert_nil phone_number.id
|
131
|
+
assert_equal 1, phone_number.phoneable_id
|
132
|
+
assert_equal 'User', phone_number.phoneable_type
|
133
|
+
assert_equal '123', phone_number.country_code
|
134
|
+
assert_equal '8675309', phone_number.number
|
135
|
+
assert_equal '999', phone_number.extension
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class PhoneNumberAfterBeingCreatedTest < Test::Unit::TestCase
|
140
|
+
def setup
|
141
|
+
@person = create_person
|
142
|
+
@phone_number = create_phone_number(:phoneable => @person, :country_code => '1', :number => '1234567890')
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_should_record_when_it_was_created
|
146
|
+
assert_not_nil @phone_number.created_at
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_should_record_when_it_was_updated
|
150
|
+
assert_not_nil @phone_number.updated_at
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_should_have_a_phoneable_association
|
154
|
+
assert_equal @person, @phone_number.phoneable
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class PhoneNumberParserTest < Test::Unit::TestCase
|
159
|
+
def setup
|
160
|
+
@person = create_person
|
161
|
+
@phone_number = new_phone_number(:phoneable => @person, :country_code => nil, :number => nil, :extension => nil)
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_should_parse_country_code_with_1_digit
|
165
|
+
@phone_number.content = '11234567890'
|
166
|
+
|
167
|
+
assert @phone_number.valid?
|
168
|
+
assert_equal '1', @phone_number.country_code
|
169
|
+
assert_equal '1234567890', @phone_number.number
|
170
|
+
assert_nil @phone_number.extension
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_should_parse_country_code_with_2_digits
|
174
|
+
@phone_number.content = '20123456789'
|
175
|
+
|
176
|
+
assert @phone_number.valid?
|
177
|
+
assert_equal '20', @phone_number.country_code
|
178
|
+
assert_equal '123456789', @phone_number.number
|
179
|
+
assert_nil @phone_number.extension
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_should_parse_country_code_with_3_digits
|
183
|
+
@phone_number.content = '21212345678'
|
184
|
+
|
185
|
+
assert @phone_number.valid?
|
186
|
+
assert_equal '212', @phone_number.country_code
|
187
|
+
assert_equal '12345678', @phone_number.number
|
188
|
+
assert_nil @phone_number.extension
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_parse_number_with_spaces
|
192
|
+
@phone_number.content = '1 123 456 7890'
|
193
|
+
|
194
|
+
assert @phone_number.valid?
|
195
|
+
assert_equal '1', @phone_number.country_code
|
196
|
+
assert_equal '1234567890', @phone_number.number
|
197
|
+
assert_nil @phone_number.extension
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_should_parse_number_with_dashes
|
201
|
+
@phone_number.content = '1-123-456-7890'
|
202
|
+
|
203
|
+
assert @phone_number.valid?
|
204
|
+
assert_equal '1', @phone_number.country_code
|
205
|
+
assert_equal '1234567890', @phone_number.number
|
206
|
+
assert_nil @phone_number.extension
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_should_parse_number_with_parentheses
|
210
|
+
@phone_number.content = '1- (123) 456-7890'
|
211
|
+
|
212
|
+
assert @phone_number.valid?
|
213
|
+
assert_equal '1', @phone_number.country_code
|
214
|
+
assert_equal '1234567890', @phone_number.number
|
215
|
+
assert_nil @phone_number.extension
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_should_parse_number_with_leading_zero
|
219
|
+
@phone_number.content = '011234567890'
|
220
|
+
|
221
|
+
assert @phone_number.valid?
|
222
|
+
assert_equal '1', @phone_number.country_code
|
223
|
+
assert_equal '1234567890', @phone_number.number
|
224
|
+
assert_nil @phone_number.extension
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_should_parse_number_with_multiple_leading_zeroes
|
228
|
+
@phone_number.content = '0011234567890'
|
229
|
+
|
230
|
+
assert @phone_number.valid?
|
231
|
+
assert_equal '1', @phone_number.country_code
|
232
|
+
assert_equal '1234567890', @phone_number.number
|
233
|
+
assert_nil @phone_number.extension
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_should_parse_extension_with_leading_ext
|
237
|
+
@phone_number.content = '11234567890 ext. 123'
|
238
|
+
|
239
|
+
assert @phone_number.valid?
|
240
|
+
assert_equal '1', @phone_number.country_code
|
241
|
+
assert_equal '1234567890', @phone_number.number
|
242
|
+
assert_equal '123', @phone_number.extension
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_should_parse_extension_with_leading_ex
|
246
|
+
@phone_number.content = '11234567890 ex:123'
|
247
|
+
|
248
|
+
assert @phone_number.valid?
|
249
|
+
assert_equal '1', @phone_number.country_code
|
250
|
+
assert_equal '1234567890', @phone_number.number
|
251
|
+
assert_equal '123', @phone_number.extension
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_should_parse_extension_with_leading_xt
|
255
|
+
@phone_number.content = '11234567890 xt: 123'
|
256
|
+
|
257
|
+
assert @phone_number.valid?
|
258
|
+
assert_equal '1', @phone_number.country_code
|
259
|
+
assert_equal '1234567890', @phone_number.number
|
260
|
+
assert_equal '123', @phone_number.extension
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_should_parse_extension_with_leading_x
|
264
|
+
@phone_number.content = '11234567890 x. 123'
|
265
|
+
|
266
|
+
assert @phone_number.valid?
|
267
|
+
assert_equal '1', @phone_number.country_code
|
268
|
+
assert_equal '1234567890', @phone_number.number
|
269
|
+
assert_equal '123', @phone_number.extension
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_should_parse_extension_with_mixed_case
|
273
|
+
@phone_number.content = '11234567890 xT 123'
|
274
|
+
|
275
|
+
assert @phone_number.valid?
|
276
|
+
assert_equal '1', @phone_number.country_code
|
277
|
+
assert_equal '1234567890', @phone_number.number
|
278
|
+
assert_equal '123', @phone_number.extension
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_should_not_be_valid_if_parse_fails
|
282
|
+
@phone_number.content = '1123456789'
|
283
|
+
|
284
|
+
assert !@phone_number.valid?
|
285
|
+
assert_equal '1', @phone_number.country_code
|
286
|
+
assert_equal '123456789', @phone_number.number
|
287
|
+
assert_nil @phone_number.extension
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class PhoneNumberParserWithDefaultCountryCodeTest < Test::Unit::TestCase
|
292
|
+
def setup
|
293
|
+
PhoneNumber.use_default_country_code_on_parse = true
|
294
|
+
|
295
|
+
@person = create_person
|
296
|
+
@phone_number = new_phone_number(:phoneable => @person, :content => '7234567890 ex. 12')
|
297
|
+
@valid = @phone_number.valid?
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_should_be_valid
|
301
|
+
assert @valid
|
302
|
+
end
|
303
|
+
|
304
|
+
def test_should_use_default_country_code
|
305
|
+
assert_equal '1', @phone_number.country_code
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_should_parse_number
|
309
|
+
assert_equal '7234567890', @phone_number.number
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_should_parse_extension
|
313
|
+
assert_equal '12', @phone_number.extension
|
314
|
+
end
|
315
|
+
|
316
|
+
def teardown
|
317
|
+
PhoneNumber.use_default_country_code_on_parse = false
|
318
|
+
end
|
319
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pluginaweek-has_phone_numbers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Pfeifer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-08 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Demonstrates a reference implementation for handling phone numbers in ActiveRecord
|
17
|
+
email: aaron@pluginaweek.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- app/models
|
26
|
+
- app/models/phone_number.rb
|
27
|
+
- db/migrate
|
28
|
+
- db/migrate/001_create_phone_numbers.rb
|
29
|
+
- lib/has_phone_numbers.rb
|
30
|
+
- test/unit
|
31
|
+
- test/unit/phone_number_test.rb
|
32
|
+
- test/factory.rb
|
33
|
+
- test/app_root
|
34
|
+
- test/app_root/app
|
35
|
+
- test/app_root/app/models
|
36
|
+
- test/app_root/app/models/person.rb
|
37
|
+
- test/app_root/db
|
38
|
+
- test/app_root/db/migrate
|
39
|
+
- test/app_root/db/migrate/002_migrate_has_phone_numbers_to_version_1.rb
|
40
|
+
- test/app_root/db/migrate/001_create_people.rb
|
41
|
+
- test/test_helper.rb
|
42
|
+
- test/functional
|
43
|
+
- test/functional/has_phone_numbers_test.rb
|
44
|
+
- CHANGELOG.rdoc
|
45
|
+
- init.rb
|
46
|
+
- LICENSE
|
47
|
+
- Rakefile
|
48
|
+
- README.rdoc
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://www.pluginaweek.org
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: pluginaweek
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Demonstrates a reference implementation for handling phone numbers in ActiveRecord
|
75
|
+
test_files:
|
76
|
+
- test/unit/phone_number_test.rb
|
77
|
+
- test/functional/has_phone_numbers_test.rb
|