dallas-has_phone_number 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,41 @@
1
1
  == HasPhoneNumber
2
2
 
3
- A simple plugin which currently just attaches a validation and setter method for each attribute argument passed to it.
3
+ A fairly simple plugin for dealing with the commonalities of phone number (and fax number) attributes.
4
+ I wanted an easy way to set them (as digit-only strings no matter what other junk got passed in) and to display them as desired using a custom getter (with +number_to_phone+ baked right in).
4
5
 
5
6
 
6
7
  == Example
7
8
 
8
- has_phone_number :phone
9
+ Setting +has_phone_number+ will give you all the default options and assumes an attribute named <tt>:phone_number</tt>.
10
+ Setting +has_fax_number+ just makes a call to +has_phone_number+ but assumes an attribute named <tt>:fax_number</tt>.
11
+
12
+ has_phone_number
13
+ has_fax_number
14
+
15
+ You can tell +has_phone_number+ what attribute you want it set on, or pass it as many attributes as you want!
16
+
17
+ has_phone_number :home_phone, :cell_phone, :other_phone
18
+
19
+ You can also pass some simple options in:
20
+
21
+ has_phone_number :validate => false # skip validation!
22
+ has_phone_number :validate_on => :update, :validate_if => proc{|record| record.something?}
23
+ has_phone_number :validation_message => "that doesn't even <em>look</em> like a phone number!"
24
+
25
+ Or, you can use the +validates_as_phone_number+ method to validate specific numbers when and where you want!
26
+
27
+ validates_as_phone_number :cell_phone, :if => proc{|record| record.home_phone.blank?}
28
+
29
+ I even went as far as allowing +method_missing+ to respond to validation methods corresponding to defined phone number attributes:
30
+
31
+ validates_other_phone :on => :update, :if => proc{|record| ![record.home_phone, record.cell_phone].any?}
32
+
33
+
34
+ == To Do
35
+
36
+ * Add support for phone extensions
37
+ * Add support for custom validations (like specifying the regular expression to check against)
38
+ * Add support for customizing the output of the getter (setting +number_to_phone+ options)
9
39
 
10
40
 
11
41
  ---
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.1
@@ -1,12 +1,15 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{has_phone_number}
5
- s.version = "1.0.0"
8
+ s.version = "1.1.1"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Dallas Reedy"]
9
- s.date = %q{2009-06-10}
12
+ s.date = %q{2009-08-25}
10
13
  s.description = %q{HasPhoneNumber is a Ruby on Rails gem for setting up phone number attributes.}
11
14
  s.email = %q{code@dallasreedy.com}
12
15
  s.extra_rdoc_files = [
@@ -19,13 +22,24 @@ Gem::Specification.new do |s|
19
22
  "VERSION",
20
23
  "has_phone_number.gemspec",
21
24
  "init.rb",
22
- "lib/has_phone_number.rb"
25
+ "lib/has_phone_number.rb",
26
+ "test/attributes_test.rb",
27
+ "test/schema.rb",
28
+ "test/test.db",
29
+ "test/test_helper.rb",
30
+ "test/validations_test.rb"
23
31
  ]
24
32
  s.homepage = %q{http://github.com/dallas/has_phone_number}
25
33
  s.rdoc_options = ["--charset=UTF-8"]
26
34
  s.require_paths = ["lib"]
27
- s.rubygems_version = %q{1.3.4}
35
+ s.rubygems_version = %q{1.3.5}
28
36
  s.summary = %q{HasPhoneNumber is a Ruby on Rails gem for setting up phone number attributes.}
37
+ s.test_files = [
38
+ "test/attributes_test.rb",
39
+ "test/schema.rb",
40
+ "test/test_helper.rb",
41
+ "test/validations_test.rb"
42
+ ]
29
43
 
30
44
  if s.respond_to? :specification_version then
31
45
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -6,49 +6,96 @@ module Dallas #:nodoc: all
6
6
  base.extend ClassMethods
7
7
  end
8
8
 
9
- module ClassMethods
10
- def has_phone_number(*attributes)
11
- @phone_numbers = []
9
+ module ClassMethods
10
+ attr_accessor :phone_number_attributes
12
11
 
13
- attributes.each do |attribute|
14
- # simple length and format validation
15
- # using validates_each to bypass ActiveRecord's odd propensity to use record.attribute
16
- # rather than record.read_attribute(attribute) or record[attribute]
17
- validates_each attribute do |record, attr_name, value|
18
- record.errors.add attr_name, 'should be a 10-digit phone number' unless record[attr_name] =~ /\d{10}/
19
- end
12
+ # the main method
13
+ def has_phone_number(*args)
14
+ @phone_number_attributes = [] unless @phone_number_attributes.is_a? Array
20
15
 
16
+ options = args.extract_options!
17
+ attributes = args
18
+ attributes << :phone_number if attributes.empty?
19
+
20
+ unless options[:validate] === false
21
+ validates_as_phone_number attributes, options
22
+ end
23
+
24
+ attributes.each do |attribute|
21
25
  # setter method to filter non-digit characters
22
26
  define_method "#{attribute}=" do |value|
23
27
  write_attribute(attribute, value.to_s.gsub(/\D+/, ''))
24
28
  end
25
-
29
+
26
30
  # getter method which automatically applies number_to_phone helper
27
- define_method attribute do |*from_database|
31
+ define_method "#{attribute}" do |*from_database|
28
32
  return nil if self[attribute].nil?
29
33
  !! from_database.first ? self[attribute] : number_to_phone(self[attribute], :area_code => true)
30
34
  end
31
-
32
- # override the default _before_type_cast so that it is
33
- # formatted using number_to_phone for form fields also
35
+
36
+ # override the default #{attribute}_before_type_cast so that it is
37
+ # formatted using number_to_phone in form fields also
34
38
  define_method "#{attribute}_before_type_cast" do |*from_database|
35
- __send__(attribute, from_database.first)
39
+ __send__(attribute, !! from_database.first)
36
40
  end
37
-
38
- @phone_numbers << attribute
41
+
42
+ @phone_number_attributes << attribute
39
43
  end
40
44
 
41
45
  include Dallas::HasPhoneNumber::InstanceMethods
42
46
  end
47
+
48
+ # define convenience method for setting the common attribute "fax_number"
49
+ def has_fax_number(*args)
50
+ options = args.extract_options!
51
+ args << :fax_number if args.empty?
52
+ has_phone_number(*args << options)
53
+ end
43
54
 
44
- def phone_numbers
45
- Array(@phone_numbers)
55
+ def phone_number_attributes
56
+ Array(@phone_number_attributes)
57
+ end
58
+
59
+ # simple length and format validation
60
+ # using validates_each to bypass ActiveRecord's odd propensity to use record.attribute
61
+ # rather than record.read_attribute(attribute) or record[attribute]
62
+ def validates_as_phone_number(*args)
63
+ options = clean_has_phone_number_options(args.extract_options!)
64
+ message = options.delete(:message)
65
+ attributes = args
66
+
67
+ validates_each attributes, options do |record, attr_name, value|
68
+ record.errors.add attr_name, message || 'should be a 10-digit phone number' unless record[attr_name] =~ /^\d{10}$/
69
+ end
70
+ end
71
+
72
+ # alias the method_missing method chain for customized validation methods
73
+ alias_method :method_missing_without_phone_number_validations, :method_missing
74
+
75
+ def method_missing_with_phone_number_validations(name, *args, &block)
76
+ return validates_as_phone_number($1, *args) if name.to_s =~ /^validates_(#{phone_number_attributes.join('|')})$/
77
+ method_missing_without_phone_number_validations(name, *args, &block)
78
+ end
79
+
80
+ alias_method :method_missing, :method_missing_with_phone_number_validations
81
+
82
+ private
83
+
84
+ # look out for keys set in the original has_(phone|fax)_number like the following:
85
+ # :validate_on => :update
86
+ # :validates_if => proc{…}
87
+ # :validation_message
88
+ def clean_has_phone_number_options(options = {})
89
+ options.inject({}) do |opts, (key, value)|
90
+ opts[key.to_s.sub(/^validat(es?|ion)_/, '').to_sym] = value
91
+ opts
92
+ end
46
93
  end
47
94
  end # ClassMethods
48
95
 
49
96
  module InstanceMethods
50
97
  def phone_numbers
51
- self.class.phone_numbers.map {|a| [a, read_attribute(a)]}
98
+ self.class.phone_number_attributes.map {|a| [a, read_attribute(a)]}
52
99
  end
53
100
  end # InstanceMethods
54
101
  end # HasPhoneNumber
@@ -0,0 +1,71 @@
1
+ require 'test_helper'
2
+
3
+ class AttributesTest < Test::Unit::TestCase
4
+ context 'Setting has_phone_number or has_fax_number' do
5
+ setup do
6
+ @attribute_names = %w(phone_number fax_number telephone facsimile phone_not_validated fax_not_validated phone_with_validation_options phone_validated_elsewhere fax_validated_elsewhere)
7
+ @user = User.new(:name => 'Dallas Reedy')
8
+
9
+ # generates a phone number in the form: nnn-nnn-nnnn
10
+ def generate_phone_number
11
+ "#{(x = ('000'..'999').to_a)[rand(x.length - 1)]}-#{x[rand(x.length - 1)]}-#{(y = ('0000'..'9999').to_a)[rand(y.length - 1)]}"
12
+ end
13
+ end
14
+
15
+ should 'add attributes names to the phone_number_attributes array' do
16
+ assert_same_elements @user.class.phone_number_attributes.map {|a| a.to_s}, @attribute_names
17
+ end
18
+
19
+ should 'automatically use :phone_number or :fax_number if no attribute names are given' do
20
+ %w(phone_number fax_number).each do |name|
21
+ assert_contains @user.class.phone_number_attributes, name.to_sym
22
+ assert_respond_to @user, "#{name}="
23
+ assert_respond_to @user, name
24
+ assert_nothing_raised do
25
+ @user.send(name, true)
26
+ end
27
+ end
28
+ end
29
+
30
+ should 'create a setter method' do
31
+ @attribute_names.each do |name|
32
+ assert_respond_to @user, "#{name}="
33
+ end
34
+ end
35
+
36
+ should 'create a getter method' do
37
+ @attribute_names.each do |name|
38
+ assert_respond_to @user, name
39
+ end
40
+ end
41
+
42
+ should 'properly set a phone number using setter method' do
43
+ @attribute_names.each do |name|
44
+ assert_send [@user, "#{name}=", number = generate_phone_number]
45
+ assert_equal number.gsub(/\D+/, ''), @user.send("#{name}", true)
46
+ end
47
+ end
48
+
49
+ should 'properly return a phone number using getter method' do
50
+ # need to run the setters again…
51
+ @attribute_names.each do |name|
52
+ assert_send [@user, "#{name}=", generate_phone_number]
53
+ end
54
+
55
+ @attribute_names.each do |name|
56
+ assert_match /^\(\d{3}\) \d{3}\-\d{4}$/, @user.send(name)
57
+ end
58
+ end
59
+
60
+ should 'return the "database stored" value if argument is passed to getter' do
61
+ # need to run the setters again…
62
+ @attribute_names.each do |name|
63
+ assert_send [@user, "#{name}=", generate_phone_number]
64
+ end
65
+
66
+ @attribute_names.each do |name|
67
+ assert_match /^\d{10}$/, @user.send(name, true)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,53 @@
1
+ ActiveRecord::Base.establish_connection(
2
+ :adapter => defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' ? 'jdbcsqlite3' : 'sqlite3',
3
+ :database => File.join(File.dirname(__FILE__), 'test.db')
4
+ )
5
+
6
+ class CreateSchema < ActiveRecord::Migration
7
+ def self.up
8
+ create_table :users, :force => true do |t|
9
+ t.string :name
10
+ t.string :phone_number, :fax_number
11
+ t.string :telephone, :facsimile
12
+ t.string :phone_not_validated, :fax_not_validated
13
+ t.string :phone_with_validation_options
14
+ t.string :phone_validated_elsewhere, :fax_validated_elsewhere
15
+ t.timestamps
16
+ end
17
+ end
18
+ end
19
+
20
+ CreateSchema.suppress_messages do
21
+ CreateSchema.migrate(:up)
22
+ end
23
+
24
+ # Define our User model
25
+ class User < ActiveRecord::Base
26
+ # defaults
27
+ has_phone_number
28
+ has_fax_number
29
+
30
+ # custom column names
31
+ has_phone_number :telephone
32
+ has_fax_number :facsimile
33
+
34
+ # validation turned off
35
+ has_phone_number :phone_not_validated, :validate => false
36
+ has_fax_number :fax_not_validated, :validate => false
37
+
38
+ # more validation options
39
+ has_phone_number :phone_with_validation_options,
40
+ :validate_if => proc{|user| !user.name.nil?},
41
+ :validates_on => :update,
42
+ :validation_message => 'is so not a valid phone number!'
43
+
44
+ # handle validation elsewhere
45
+ has_phone_number :phone_validated_elsewhere, :validate => false
46
+ has_fax_number :fax_validated_elsewhere, :validate => false
47
+
48
+ # default validation method
49
+ validates_as_phone_number :phone_validated_elsewhere, :message => "didn't pass validations elsewhere"
50
+
51
+ # auto-defined validation method
52
+ validates_fax_validated_elsewhere :on => :update, :message => "didn't pass validations from auto-defined validation method"
53
+ end
Binary file
@@ -0,0 +1,11 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $: << File.dirname(__FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'activerecord'
7
+ require 'shoulda'
8
+ require 'action_view'
9
+ require 'has_phone_number'
10
+ require 'schema'
11
+ begin; require 'redgreen'; rescue LoadError; end
@@ -0,0 +1,108 @@
1
+ require 'test_helper'
2
+
3
+ class ValidationsTest < Test::Unit::TestCase
4
+ context 'Setting has_phone_number or has_fax_number' do
5
+ setup do
6
+ @attribute_names = %w(phone_number fax_number telephone facsimile phone_not_validated fax_not_validated phone_with_validation_options phone_validated_elsewhere fax_validated_elsewhere)
7
+ @not_validated = %w(phone_not_validated fax_not_validated)
8
+ @validated_on_update = %w(phone_with_validation_options fax_validated_elsewhere)
9
+ @auto_validated = @attribute_names - @not_validated - @validated_on_update
10
+
11
+ @user = User.new(:name => 'Dallas Reedy')
12
+
13
+ # generates a phone number in the form: nnn-nnn-nnnn
14
+ def generate_phone_number
15
+ "#{(x = ('000'..'999').to_a)[rand(x.length - 1)]}-#{x[rand(x.length - 1)]}-#{(y = ('0000'..'9999').to_a)[rand(y.length - 1)]}"
16
+ end
17
+
18
+ def set_fields(fields)
19
+ fields.each do |name|
20
+ @user.send("#{name}=", generate_phone_number)
21
+ end
22
+ end
23
+ end
24
+
25
+ should 'automatically add a validation unless told not to' do
26
+ @auto_validated.each do |name|
27
+ assert_equal false, @user.valid?
28
+ assert @user.errors.invalid?(name.to_s)
29
+
30
+ @user.send("#{name}=", 'gibberish.')
31
+ assert_equal false, @user.valid?
32
+ assert @user.errors.invalid?(name.to_s)
33
+
34
+ @user.send("#{name}=", generate_phone_number)
35
+ end
36
+ assert @user.save
37
+ end
38
+
39
+ should 'not validate if told not to' do
40
+ set_fields @auto_validated
41
+
42
+ @not_validated.each do |name|
43
+ assert @user.valid?
44
+ assert @user.errors.empty?
45
+
46
+ @user.send("#{name}=", 'gibberish.')
47
+ @user.valid?
48
+ assert @user.errors.empty?
49
+ end
50
+ assert @user.save
51
+ end
52
+
53
+ should 'apply and adhere to validation-specific options set for the default validation' do
54
+ set_fields @auto_validated + ['fax_validated_elsewhere']
55
+
56
+ # test that validation fails on :update
57
+ assert @user.save
58
+ assert_equal false, @user.valid?
59
+ assert @user.errors.invalid?(:phone_with_validation_options)
60
+
61
+ # test invalid data
62
+ @user.phone_with_validation_options = 'gibberish.'
63
+ assert_equal false, @user.valid?
64
+ assert @user.errors.invalid?(:phone_with_validation_options)
65
+
66
+ # test our custom error message
67
+ assert_equal 'is so not a valid phone number!', @user.errors.on(:phone_with_validation_options)
68
+
69
+ # test our custom error :if option
70
+ @user.name = nil
71
+ assert @user.valid?
72
+
73
+ # test saving valid record
74
+ @user.name = 'Dallas Reedy'
75
+ @user.phone_with_validation_options = generate_phone_number
76
+ assert @user.save
77
+ end
78
+
79
+ should 'respond to validates_as_phone_number and adhere to specified options' do
80
+ set_fields @auto_validated - ['phone_validated_elsewhere']
81
+
82
+ # test our custom error message
83
+ assert_equal false, @user.valid?
84
+ assert @user.errors.invalid?(:phone_validated_elsewhere)
85
+ assert_equal "didn't pass validations elsewhere", @user.errors.on(:phone_validated_elsewhere)
86
+
87
+ # test saving valid record
88
+ @user.phone_validated_elsewhere = generate_phone_number
89
+ assert @user.save
90
+ end
91
+
92
+ should 'respond to validates_#{attribute_name} and adhere to specified options' do
93
+ set_fields @auto_validated + ['phone_with_validation_options']
94
+
95
+ # test that validation fails on :update
96
+ assert @user.save
97
+ assert_equal false, @user.valid?
98
+ assert @user.errors.invalid?(:fax_validated_elsewhere)
99
+
100
+ # test for our custom error message
101
+ assert_equal "didn't pass validations from auto-defined validation method", @user.errors.on(:fax_validated_elsewhere)
102
+
103
+ # test saving valid record
104
+ @user.fax_validated_elsewhere = generate_phone_number
105
+ assert @user.save
106
+ end
107
+ end
108
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dallas-has_phone_number
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dallas Reedy
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-10 00:00:00 -07:00
12
+ date: 2009-08-25 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -29,8 +29,14 @@ files:
29
29
  - has_phone_number.gemspec
30
30
  - init.rb
31
31
  - lib/has_phone_number.rb
32
+ - test/attributes_test.rb
33
+ - test/schema.rb
34
+ - test/test.db
35
+ - test/test_helper.rb
36
+ - test/validations_test.rb
32
37
  has_rdoc: false
33
38
  homepage: http://github.com/dallas/has_phone_number
39
+ licenses:
34
40
  post_install_message:
35
41
  rdoc_options:
36
42
  - --charset=UTF-8
@@ -51,9 +57,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
57
  requirements: []
52
58
 
53
59
  rubyforge_project:
54
- rubygems_version: 1.2.0
60
+ rubygems_version: 1.3.5
55
61
  signing_key:
56
62
  specification_version: 3
57
63
  summary: HasPhoneNumber is a Ruby on Rails gem for setting up phone number attributes.
58
- test_files: []
59
-
64
+ test_files:
65
+ - test/attributes_test.rb
66
+ - test/schema.rb
67
+ - test/test_helper.rb
68
+ - test/validations_test.rb