pluginaweek-has_phone_numbers 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ class Person < ActiveRecord::Base
2
+ has_phone_numbers
3
+ end
@@ -0,0 +1,11 @@
1
+ class CreatePeople < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :people do |t|
4
+ t.string :name
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :people
10
+ end
11
+ 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
@@ -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