rcap 0.4 → 1.0.0.rc.1

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