rcap 0.4 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +7 -0
  2. data/CHANGELOG.rdoc +6 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +104 -85
  5. data/Rakefile +35 -0
  6. data/lib/rcap.rb +21 -14
  7. data/lib/rcap/alert.rb +26 -325
  8. data/lib/rcap/cap_1_1/alert.rb +363 -0
  9. data/lib/rcap/cap_1_1/area.rb +180 -0
  10. data/lib/rcap/cap_1_1/circle.rb +81 -0
  11. data/lib/rcap/cap_1_1/event_code.rb +22 -0
  12. data/lib/rcap/cap_1_1/geocode.rb +22 -0
  13. data/lib/rcap/cap_1_1/info.rb +470 -0
  14. data/lib/rcap/cap_1_1/parameter.rb +68 -0
  15. data/lib/rcap/cap_1_1/point.rb +55 -0
  16. data/lib/rcap/cap_1_1/polygon.rb +89 -0
  17. data/lib/rcap/cap_1_1/resource.rb +145 -0
  18. data/lib/rcap/cap_1_2/alert.rb +363 -0
  19. data/lib/rcap/cap_1_2/area.rb +180 -0
  20. data/lib/rcap/cap_1_2/circle.rb +81 -0
  21. data/lib/rcap/cap_1_2/event_code.rb +22 -0
  22. data/lib/rcap/cap_1_2/geocode.rb +22 -0
  23. data/lib/rcap/cap_1_2/info.rb +472 -0
  24. data/lib/rcap/cap_1_2/parameter.rb +68 -0
  25. data/lib/rcap/cap_1_2/point.rb +55 -0
  26. data/lib/rcap/cap_1_2/polygon.rb +90 -0
  27. data/lib/rcap/cap_1_2/resource.rb +147 -0
  28. data/lib/rcap/utilities.rb +14 -9
  29. data/lib/rcap/validations.rb +39 -7
  30. data/lib/rcap/version.rb +3 -0
  31. data/rcap.gemspec +30 -0
  32. data/spec/alert_spec.rb +109 -172
  33. data/spec/cap_1_1/alert_spec.rb +222 -0
  34. data/spec/cap_1_1/area_spec.rb +247 -0
  35. data/spec/cap_1_1/circle_spec.rb +88 -0
  36. data/spec/{geocode_spec.rb → cap_1_1/geocode_spec.rb} +8 -8
  37. data/spec/{info_spec.rb → cap_1_1/info_spec.rb} +143 -75
  38. data/spec/{point_spec.rb → cap_1_1/point_spec.rb} +8 -8
  39. data/spec/cap_1_1/polygon_spec.rb +97 -0
  40. data/spec/{resource_spec.rb → cap_1_1/resource_spec.rb} +24 -24
  41. data/spec/cap_1_2/alert_spec.rb +233 -0
  42. data/spec/cap_1_2/area_spec.rb +248 -0
  43. data/spec/cap_1_2/circle_spec.rb +95 -0
  44. data/spec/cap_1_2/geocode_spec.rb +38 -0
  45. data/spec/cap_1_2/info_spec.rb +338 -0
  46. data/spec/cap_1_2/point_spec.rb +46 -0
  47. data/spec/cap_1_2/polygon_spec.rb +102 -0
  48. data/spec/cap_1_2/resource_spec.rb +161 -0
  49. data/spec/spec.opts +2 -0
  50. data/spec/validations_spec.rb +80 -7
  51. metadata +122 -37
  52. data/lib/rcap/area.rb +0 -156
  53. data/lib/rcap/circle.rb +0 -78
  54. data/lib/rcap/event_code.rb +0 -20
  55. data/lib/rcap/geocode.rb +0 -20
  56. data/lib/rcap/info.rb +0 -437
  57. data/lib/rcap/parameter.rb +0 -66
  58. data/lib/rcap/point.rb +0 -53
  59. data/lib/rcap/polygon.rb +0 -77
  60. data/lib/rcap/resource.rb +0 -143
  61. data/spec/area_spec.rb +0 -179
  62. data/spec/circle_spec.rb +0 -88
  63. data/spec/polygon_spec.rb +0 -68
@@ -0,0 +1,68 @@
1
+ module RCAP
2
+ module CAP_1_2
3
+ # A Parameter object is valid if
4
+ # * it has a name
5
+ # * it has a value
6
+ class Parameter
7
+ include Validation
8
+
9
+ validates_presence_of( :name, :value )
10
+
11
+ attr_accessor( :name, :value )
12
+
13
+ XML_ELEMENT_NAME = "parameter" # :nodoc:
14
+ NAME_ELEMENT_NAME = "valueName" # :nodoc:
15
+ VALUE_ELEMENT_NAME = "value" # :nodoc:
16
+
17
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
18
+ NAME_XPATH = "cap:#{ NAME_ELEMENT_NAME }" # :nodoc:
19
+ VALUE_XPATH = "cap:#{ VALUE_ELEMENT_NAME }" # :nodoc:
20
+
21
+ def initialize( attributes = {} )
22
+ @name = attributes[ :name ]
23
+ @value = attributes[ :value ]
24
+ end
25
+
26
+ def to_xml_element # :nodoc:
27
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
28
+ xml_element.add_element( NAME_ELEMENT_NAME ).add_text( self.name )
29
+ xml_element.add_element( VALUE_ELEMENT_NAME ).add_text( self.value )
30
+ xml_element
31
+ end
32
+
33
+ def to_xml # :nodoc:
34
+ self.to_xml_element.to_s
35
+ end
36
+
37
+ def inspect # :nodoc:
38
+ "#{ self.name }: #{ self.value }"
39
+ end
40
+
41
+ # Returns a string representation of the parameter of the form
42
+ # name: value
43
+ def to_s
44
+ self.inspect
45
+ end
46
+
47
+ def self.from_xml_element( parameter_xml_element ) # :nodoc:
48
+ Parameter.new( :name => RCAP.xpath_text( parameter_xml_element, NAME_XPATH, Alert::XMLNS ),
49
+ :value => RCAP.xpath_text( parameter_xml_element, VALUE_XPATH, Alert::XMLNS ))
50
+ end
51
+
52
+ # Two parameters are equivalent if they have the same name and value.
53
+ def ==( other )
54
+ [ self.name, self.value ] == [ other.name, other.value ]
55
+ end
56
+
57
+ def to_h # :nodoc:
58
+ RCAP.attribute_values_to_hash(
59
+ [ @name, @value ])
60
+ end
61
+
62
+ def self.from_h( hash ) # :nodoc:
63
+ key = hash.keys.first
64
+ self.new( :name => key, :value => hash[ key ])
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ module RCAP
2
+ module CAP_1_2
3
+ # A Point object is valid if
4
+ # * it has a lattitude within the minimum and maximum lattitude values
5
+ # * it has a longitude within the minimum and maximum longitude values
6
+ class Point
7
+ include Validation
8
+
9
+ MAX_LONGITUDE = 180
10
+ MIN_LONGITUDE = -180
11
+ MAX_LATTITUDE = 90
12
+ MIN_LATTITUDE= -90
13
+
14
+ attr_accessor( :lattitude )
15
+ attr_accessor( :longitude )
16
+
17
+ validates_numericality_of( :lattitude, :longitude )
18
+ validates_inclusion_of( :lattitude, :in => MIN_LATTITUDE..MAX_LATTITUDE )
19
+ validates_inclusion_of( :longitude, :in => MIN_LONGITUDE..MAX_LONGITUDE)
20
+
21
+ def initialize( attributes = {} )
22
+ @lattitude = attributes[ :lattitude ]
23
+ @longitude = attributes[ :longitude ]
24
+ end
25
+
26
+ # Returns a string representation of the point of the form
27
+ # lattitude,longitude
28
+ def to_s
29
+ "#{ self.lattitude },#{ self.longitude }"
30
+ end
31
+
32
+ def inspect # :nodoc:
33
+ '('+self.to_s+')'
34
+ end
35
+
36
+ # Two points are equivalent if they have the same lattitude and longitude
37
+ def ==( other )
38
+ [ self.lattitude, self.longitude ] == [ other.lattitude, other.longitude ]
39
+ end
40
+
41
+ LATTITUDE_KEY = 'lattitude_hash' # :nodoc:
42
+ LONGITUDE_KEY = 'longitude_hash' # :nodoc:
43
+
44
+ def to_h # :nodoc:
45
+ RCAP.attribute_values_to_hash(
46
+ [ LATTITUDE_KEY, self.lattitude ],
47
+ [ LONGITUDE_KEY, self.longitude ])
48
+ end
49
+
50
+ def self.from_h( point_hash ) # :nodoc:
51
+ self.new( :lattitude => point_hash[ LATTITUDE_KEY ], :longitude => point_hash[ LONGITUDE_KEY ])
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
1
+ module RCAP
2
+ module CAP_1_2
3
+ # A Polygon object is valid if
4
+ # * it has a minimum of three points
5
+ # * each Point object in the points collection is valid
6
+ class Polygon
7
+ include Validation
8
+
9
+ # Collection of Point objects.
10
+ attr_reader( :points )
11
+
12
+ validates_collection_of( :points )
13
+ validates_length_of( :points, :minimum => 4 )
14
+ validates_equality_of_first_and_last( :points )
15
+
16
+ XML_ELEMENT_NAME = 'polygon' # :nodoc:
17
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
18
+
19
+ def initialize( attributes = {})
20
+ @points = Array( attributes[ :points ])
21
+ end
22
+
23
+ # Creates a new Point object and adds it to the points array. The
24
+ # poitn_attributes are passed as a parameter to Point.new.
25
+ def add_point( point_attributes = {})
26
+ point = Point.new( point_attributes )
27
+ self.points << point
28
+ point
29
+ end
30
+
31
+ # Returns a string representation of the polygon of the form
32
+ # points[0] points[1] points[2] ... points[n-1] points[0]
33
+ # where each point is formatted with Point#to_s
34
+ def to_s
35
+ @points.join( ' ' )
36
+ end
37
+
38
+ def inspect # :nodoc:
39
+ "(#{ @points.map{|point| point.inspect}.join(', ')})"
40
+ end
41
+
42
+ def to_xml_element # :nodoc:
43
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
44
+ xml_element.add_text( self.to_s )
45
+ xml_element
46
+ end
47
+
48
+ # Two polygons are equivalent if their collection of points is equivalent.
49
+ def ==( other )
50
+ self.points == other.points
51
+ end
52
+
53
+ def self.parse_polygon_string( polygon_string ) # :nodoc:
54
+ polygon_string.split( ' ' ).map{ |coordinate_string| coordinate_string.split( ',' ).map{|coordinate| coordinate.to_f }}
55
+ end
56
+
57
+ def self.from_xml_element( polygon_xml_element ) # :nodoc:
58
+ if !polygon_xml_element.text.nil?
59
+ coordinates = self.parse_polygon_string( polygon_xml_element.text )
60
+ points = coordinates.map{ |lattitude, longitude| Point.new( :lattitude => lattitude, :longitude => longitude )}
61
+ polygon = self.new( :points => points )
62
+ else
63
+ self.new
64
+ end
65
+ end
66
+
67
+
68
+ def to_yaml( options = {} ) # :nodoc:
69
+ yaml_points = self.points.map{ |point| [ point.lattitude, point.longitude ]}
70
+ def yaml_points.to_yaml_style; :inline; end
71
+
72
+ yaml_points.to_yaml( options )
73
+ end
74
+
75
+ def self.from_yaml_data( polygon_yaml_data ) # :nodoc:
76
+ self.new( :points => Array( polygon_yaml_data ).map{ |lattitude, longitude| Point.new( :lattitude => lattitude, :longitude => longitude )})
77
+ end
78
+
79
+ POINTS_KEY = 'points' # :nodoc:
80
+
81
+ def to_h # :nodoc:
82
+ { POINTS_KEY => self.points.map{ |point| point.to_h }}
83
+ end
84
+
85
+ def self.from_h( polygon_hash ) # :nodoc:
86
+ self.new( :points => polygon_hash[ POINTS_KEY ].map{ |point_hash| Point.from_h( point_hash )})
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,147 @@
1
+ module RCAP
2
+ module CAP_1_2
3
+ # A Resourse object is valid if
4
+ # * it has a resource description
5
+ class Resource
6
+ include Validation
7
+
8
+ # Resource Description
9
+ attr_accessor( :resource_desc )
10
+ # MIME type as described in RFC 2046
11
+ attr_accessor( :mime_type )
12
+ # Expressed in bytes
13
+ attr_accessor( :size )
14
+ # Resource location
15
+ attr_accessor( :uri )
16
+ # Dereferenced URI - contents of URI Base64 encoded
17
+ attr_accessor( :deref_uri )
18
+ # SHA-1 hash of contents of resource
19
+ attr_accessor( :digest )
20
+
21
+ validates_presence_of( :resource_desc )
22
+ validates_presence_of( :mime_type )
23
+
24
+ XML_ELEMENT_NAME = 'resource' # :nodoc:
25
+ MIME_TYPE_ELEMENT_NAME = 'mimeType' # :nodoc:
26
+ SIZE_ELEMENT_NAME = 'size' # :nodoc:
27
+ URI_ELEMENT_NAME = 'uri' # :nodoc:
28
+ DEREF_URI_ELEMENT_NAME = 'derefUri' # :nodoc:
29
+ DIGEST_ELEMENT_NAME = 'digest' # :nodoc:
30
+ RESOURCE_DESC_ELEMENT_NAME = 'resourceDesc' # :nodoc:
31
+
32
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
33
+ MIME_TYPE_XPATH = "cap:#{ MIME_TYPE_ELEMENT_NAME }" # :nodoc:
34
+ SIZE_XPATH = "cap:#{ SIZE_ELEMENT_NAME }" # :nodoc:
35
+ URI_XPATH = "cap:#{ URI_ELEMENT_NAME }" # :nodoc:
36
+ DEREF_URI_XPATH = "cap:#{ DEREF_URI_ELEMENT_NAME }" # :nodoc:
37
+ DIGEST_XPATH = "cap:#{ DIGEST_ELEMENT_NAME }" # :nodoc:
38
+ RESOURCE_DESC_XPATH = "cap:#{ RESOURCE_DESC_ELEMENT_NAME }" # :nodoc:
39
+
40
+ def initialize( attributes = {} )
41
+ @mime_type = attributes[ :mime_type ]
42
+ @size = attributes[ :size ]
43
+ @uri = attributes[ :uri ]
44
+ @deref_uri = attributes[ :deref_uri ]
45
+ @digest = attributes[ :digest ]
46
+ @resource_desc = attributes[ :resource_desc ]
47
+ end
48
+
49
+ def to_xml_element # :nodoc:
50
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
51
+ xml_element.add_element( RESOURCE_DESC_ELEMENT_NAME ).add_text( self.resource_desc )
52
+ xml_element.add_element( MIME_TYPE_ELEMENT_NAME ).add_text( self.mime_type ) if self.mime_type
53
+ xml_element.add_element( SIZE_ELEMENT_NAME ).add_text( self.size ) if self.size
54
+ xml_element.add_element( URI_ELEMENT_NAME ).add_text( self.uri ) if self.uri
55
+ xml_element.add_element( DEREF_URI_ELEMENT_NAME ).add_text( self.deref_uri ) if self.deref_uri
56
+ xml_element.add_element( DIGEST_ELEMENT_NAME ).add_text( self.digest ) if self.digest
57
+ xml_element
58
+ end
59
+
60
+ # If size is defined returns the size in kilobytes
61
+ def size_in_kb
62
+ if self.size
63
+ self.size.to_f/1024
64
+ end
65
+ end
66
+
67
+ def to_xml # :nodoc:
68
+ self.to_xml_element.to_s
69
+ end
70
+
71
+ def inspect # :nodoc:
72
+ [ self.resource_desc, self.uri, self.mime_type, self.size ? format( "%.1fKB", self.size_in_kb ) : nil ].compact.join(' - ')
73
+ end
74
+
75
+ # Returns a string representation of the resource of the form
76
+ # resource_desc
77
+ def to_s
78
+ self.resource_desc
79
+ end
80
+
81
+ def self.from_xml_element( resource_xml_element ) # :nodoc:
82
+ resource = self.new( :resource_desc => RCAP.xpath_text( resource_xml_element, RESOURCE_DESC_XPATH, Alert::XMLNS ),
83
+ :uri => RCAP.xpath_text( resource_xml_element, URI_XPATH, Alert::XMLNS ),
84
+ :mime_type => RCAP.xpath_text( resource_xml_element, MIME_TYPE_XPATH, Alert::XMLNS ),
85
+ :deref_uri => RCAP.xpath_text( resource_xml_element, DEREF_URI_XPATH, Alert::XMLNS ),
86
+ :size => RCAP.xpath_text( resource_xml_element, SIZE_XPATH, Alert::XMLNS ),
87
+ :digest => RCAP.xpath_text( resource_xml_element, DIGEST_XPATH, Alert::XMLNS ))
88
+ end
89
+
90
+ RESOURCE_DESC_YAML = "Resource Description" # :nodoc:
91
+ URI_YAML = "URI" # :nodoc:
92
+ MIME_TYPE_YAML = "Mime Type" # :nodoc:
93
+ DEREF_URI_YAML = "Derefrenced URI Data" # :nodoc:
94
+ SIZE_YAML = "Size" # :nodoc:
95
+ DIGEST_YAML = "Digest" # :nodoc:
96
+
97
+ def to_yaml( options ) # :nodoc:
98
+ RCAP.attribute_values_to_hash(
99
+ [ RESOURCE_DESC_YAML, self.resource_desc ],
100
+ [ URI_YAML, self.uri ],
101
+ [ MIME_TYPE_YAML, self.mime_type ],
102
+ [ DEREF_URI_YAML, self.deref_uri ],
103
+ [ SIZE_YAML, self.size ],
104
+ [ DIGEST_YAML, self.digest ]
105
+ ).to_yaml( options )
106
+ end
107
+
108
+ def self.from_yaml_data( resource_yaml_data ) # :nodoc:
109
+ self.new(
110
+ :resource_desc => reource_yaml_data[ RESOURCE_DESC_YAML ],
111
+ :uri => reource_yaml_data[ URI_YAML ],
112
+ :mime_type => reource_yaml_data[ MIME_TYPE_YAML ],
113
+ :deref_uri => reource_yaml_data[ DEREF_URI_YAML ],
114
+ :size => reource_yaml_data[ SIZE_YAML ],
115
+ :digest => reource_yaml_data[ DIGEST_YAML ]
116
+ )
117
+ end
118
+
119
+ RESOURCE_DESC_KEY = 'resource_desc' # :nodoc:
120
+ URI_KEY = 'uri' # :nodoc:
121
+ MIME_TYPE_KEY = 'mime_type' # :nodoc:
122
+ DEREF_URI_KEY = 'deref_uri' # :nodoc:
123
+ SIZE_KEY = 'size' # :nodoc:
124
+ DIGEST_KEY = 'digest' # :nodoc:
125
+
126
+ def to_h # :nodoc:
127
+ RCAP.attribute_values_to_hash(
128
+ [ RESOURCE_DESC_KEY, self.resource_desc ],
129
+ [ URI_KEY, self.uri],
130
+ [ MIME_TYPE_KEY, self.mime_type],
131
+ [ DEREF_URI_KEY, self.deref_uri],
132
+ [ SIZE_KEY, self.size ],
133
+ [ DIGEST_KEY, self.digest ])
134
+ end
135
+
136
+ def self.from_h( resource_hash ) # :nodoc:
137
+ self.new(
138
+ :resource_desc => resource_hash[ RESOURCE_DESC_KEY ],
139
+ :uri => resource_hash[ URI_KEY ],
140
+ :mime_type => resource_hash[ MIME_TYPE_KEY ],
141
+ :deref_uri => resource_hash[ DEREF_URI_KEY ],
142
+ :size => resource_hash[ SIZE_KEY ],
143
+ :digest => resource_hash[ DIGEST_KEY ])
144
+ end
145
+ end
146
+ end
147
+ end
@@ -19,7 +19,7 @@ class String # :nodoc:
19
19
  end
20
20
 
21
21
  def unpack_cap_list
22
- self.split( CAP_LIST_REGEX ).reject{ |match| match == "" || match =~ WHITESPACE_REGEX }
22
+ self.split( CAP_LIST_REGEX ).reject{ |match| match == "" || match =~ WHITESPACE_REGEX }
23
23
  end
24
24
  end
25
25
 
@@ -33,7 +33,8 @@ class Time # :nodoc:
33
33
  RCAP_ZONE_FORMAT = "%+03i:00"
34
34
 
35
35
  def to_s_for_cap
36
- self.strftime( RCAP_TIME_FORMAT ) + format( RCAP_ZONE_FORMAT , self.utc_hours_offset )
36
+ t = self.strftime( RCAP_TIME_FORMAT ) + format( RCAP_ZONE_FORMAT , self.utc_hours_offset )
37
+ t.sub(/\+(00:\d\d)$/, '-\1')
37
38
  end
38
39
 
39
40
  def utc_hours_offset
@@ -42,17 +43,21 @@ class Time # :nodoc:
42
43
  end
43
44
 
44
45
  module RCAP # :nodoc:
45
- def self.xpath_text( xml_element, xpath )
46
- element = self.xpath_first( xml_element, xpath )
46
+ def self.generate_identifier
47
+ UUIDTools::UUID.random_create.to_s
48
+ end
49
+
50
+ def self.xpath_text( xml_element, xpath, namespace )
51
+ element = self.xpath_first( xml_element, xpath, namespace )
47
52
  element.text if element
48
53
  end
49
54
 
50
- def self.xpath_first( xml_element, xpath )
51
- REXML::XPath.first( xml_element, xpath, { 'cap' => RCAP::XMLNS })
55
+ def self.xpath_first( xml_element, xpath, namespace )
56
+ REXML::XPath.first( xml_element, xpath, { 'cap' => namespace })
52
57
  end
53
58
 
54
- def self.xpath_match( xml_element, xpath )
55
- REXML::XPath.match( xml_element, xpath, { 'cap' => RCAP::XMLNS })
59
+ def self.xpath_match( xml_element, xpath, namespace )
60
+ REXML::XPath.match( xml_element, xpath, { 'cap' => namespace })
56
61
  end
57
62
 
58
63
  def self.format_lines_for_inspect( header, inspect_string )
@@ -65,7 +70,7 @@ module RCAP # :nodoc:
65
70
  end
66
71
 
67
72
  def self.attribute_values_to_hash( *attribute_values )
68
- Hash[ *attribute_values.reject{ |key, value| value.blank? }.flatten( 1 )]
73
+ Hash[ *attribute_values.reject{ |key, value| value.blank? }.flatten( 1 )]
69
74
  end
70
75
 
71
76
  def self.to_s_for_cap( object )
@@ -1,12 +1,12 @@
1
- module Validation # :nodoc:
1
+ module Validation # :nodoc:
2
2
  module ClassMethods # :nodoc:
3
3
 
4
4
  CAP_NUMBER_REGEX = Regexp.new( '^-{0,1}\d*\.{0,1}\d+$' )
5
5
  CAP_INTEGER_REGEX = Regexp.new( '\-{0,1}A[+-]?\d+\Z' )
6
6
 
7
7
  def validates_inclusion_of( *attributes )
8
- options = {
9
- :message => 'is not in the required range'
8
+ options = {
9
+ :message => 'is not in the required range'
10
10
  }.merge!( attributes.extract_options! )
11
11
 
12
12
  validates_each( *attributes ) do |object, attribute, value|
@@ -75,14 +75,33 @@ module Validation # :nodoc:
75
75
  }.merge!( attributes.extract_options! )
76
76
 
77
77
  validates_each( *attributes ) do |object, attribute, value|
78
- contingent_on_value = object.send( options[ :on ])
79
- next if ( value.nil? && options[ :allow_nil ]) || ( value.blank? && options[ :allow_blank ])
80
- unless value.blank? || !value.blank? && !contingent_on_value.blank? && ( options[ :with_value ].nil? || contingent_on_value == options[ :with_value ])
78
+ next if value.blank?
79
+ contingent_on_attribute = object.send( options[ :on ])
80
+ contingent_on_attribute_value = options[ :with_value ]
81
+
82
+ if contingent_on_attribute.nil? || !contingent_on_attribute_value.nil? && contingent_on_attribute_value != contingent_on_attribute
81
83
  object.errors[ attribute ] << options[ :message ].gsub( /:attribute/, options[ :on ].to_s )
82
84
  end
83
85
  end
84
86
  end
85
87
 
88
+ def validates_conditional_presence_of( *attributes )
89
+ options = {
90
+ :message => 'is not defined but is required by :contingent_attribute'
91
+ }.merge!( attributes.extract_options! )
92
+
93
+ validates_each( *attributes ) do |object, attribute, value|
94
+ contingent_attribute_value = object.send( options[ :when ] )
95
+ required_contingent_attribute_value = options[ :is ]
96
+
97
+ next if contingent_attribute_value.nil? || contingent_attribute_value != required_contingent_attribute_value && !options[ :is ].blank?
98
+ if value.blank?
99
+ object.errors[ attribute ] << options[ :message ].gsub( /:contingent_attribute/, options[ :whenn ].to_s )
100
+ end
101
+ end
102
+ end
103
+
104
+
86
105
  def validates_numericality_of( *attributes )
87
106
  options = {
88
107
  :message => 'is not a number',
@@ -94,7 +113,7 @@ module Validation # :nodoc:
94
113
  next if (value.nil? && options[ :allow_nil ]) || (value.blank? && options[ :allow_blank ])
95
114
  unless ( value.to_s =~ re ) &&
96
115
  ( options[ :greater_than ].nil? || value && value > options[ :greater_than ])
97
- object.errors[ attribute ] << options[ :message ]
116
+ object.errors[ attribute ] << options[ :message ]
98
117
  end
99
118
  end
100
119
  end
@@ -112,5 +131,18 @@ module Validation # :nodoc:
112
131
  end
113
132
  end
114
133
  end
134
+
135
+ def validates_equality_of_first_and_last( *attributes )
136
+ options = {
137
+ :message => 'does not have equal first and last elements'
138
+ }.merge!( attributes.extract_options! )
139
+
140
+ validates_each( *attributes ) do |object, attribute, collection|
141
+ next if ( collection.nil? && options[ :allow_nil ]) || ( collection.blank? && options[ :allow_blank ])
142
+ unless collection.first == collection.last
143
+ object.errors[ attribute ] << options[ :message ]
144
+ end
145
+ end
146
+ end
115
147
  end
116
148
  end