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 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