<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>lib/predicates/size.rb</filename>
    </added>
    <added>
      <filename>lib/semantic_attributes/locale/en.yml</filename>
    </added>
    <added>
      <filename>test/unit/predicates/base_test.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -6,9 +6,4 @@ ActiveRecord::Base.class_eval do
   include ActiveRecord::ValidationRecursionControl
 end
 
-# localization mock
-ActiveRecord::Base.class_eval do
-  unless respond_to? :_
-    def _(s); s; end
-  end
-end
+I18n.load_path &lt;&lt; File.dirname(__FILE__) + '/lib/semantic_attributes/locale/en.yml'</diff>
      <filename>init.rb</filename>
    </modified>
    <modified>
      <diff>@@ -8,7 +8,11 @@ class Predicates::Aliased &lt; Predicates::Enumerated
     options[v]
   end
 
-  def from_human(v)
+  def validate(value, record)
+    self.options.has_value? value
+  end
+
+  def normalize(v)
     options.index(v)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/aliased.rb</filename>
    </modified>
    <modified>
      <diff>@@ -15,7 +15,7 @@ class Predicates::Association &lt; Predicates::Base
   attr_accessor :max
 
   def error_message
-    @error_message || 'is required.'
+    @error_message || :required
   end
 
   def validate(value, record)</diff>
      <filename>lib/predicates/association.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,9 +13,21 @@ module Predicates
     ##
 
     # the error string when validation fails
-    attr_accessor :error_message
+    def error_message
+      @error_message || :invalid
+    end
+    attr_writer :error_message
     alias_accessor :message, :error_message
 
+    # available interpolation variables for the error message (see I18n.translate)
+    def error_binds
+      {}
+    end
+    
+    def error
+      error_message.is_a?(Symbol) ? I18n.t(error_message, error_binds.merge(:scope =&gt; 'semantic-attributes.errors.messages')) : error_message
+    end
+
     # a condition to restrict when validation should occur. if it returns false, the validation will not happen.
     # if the value is a proc, then the proc will be called and the record object passed as the argument
     # if the value is a symbol, then a method by that name will be called on the record
@@ -55,18 +67,17 @@ module Predicates
     def validate(value, record)
       raise NotImplementedError
     end
+    
+    # define this in the concrete class to provide a method for normalizing human inputs.
+    # this gives you the ability to be very forgiving of formatting variations in form data.
+    def normalize(value)
+      value
+    end
 
     # define this in the concrete class to provide a method for converting from a storage format to a human readable format
     # this is good for presenting your clean, logical data in a way that people like to read.
     def to_human(value)
       value
     end
-
-    # define this in the concrete class to provide a method for converting from a human readable format to a storage format.
-    # this is good for letting people do fuzzy searches, or add values through a form in a variety of formats, but still retain consistent data.
-    # really, consider this a normalization routine for form input.
-    def from_human(value)
-      value
-    end
   end
 end</diff>
      <filename>lib/predicates/base.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,9 +1,23 @@
 # Blacklisted is the inverse of Enumerated. This is how you can say that a field may _not_ be certain values.
 #
 # ==Example
-#   field_is_blacklisted :options =&gt; ['disallowed_value_one', 'disallowed_value_two']
-class Predicates::Blacklisted &lt; Predicates::Enumerated
-  def validate(*args)
-    !super
+#   field_is_blacklisted :not =&gt; ['disallowed_value_one', 'disallowed_value_two']
+class Predicates::Blacklisted &lt; Predicates::Base
+  # whether the comparison is case-sensitive
+  attr_accessor :case_sensitive
+
+  # the blacklist
+  attr_accessor :restricted
+
+  def error_message
+    @error_message || :exclusion
   end
-end
\ No newline at end of file
+
+  def validate(val, record)
+    if self.case_sensitive
+      !self.restricted.include? val
+    else
+      !self.restricted.any? {|r| r.to_s.downcase == val.to_s.downcase }
+    end
+  end
+end</diff>
      <filename>lib/predicates/blacklisted.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,7 @@ require 'uri'
 # Defines a field as a simple domain (not URL).
 class Predicates::Domain &lt; Predicates::Base
   def error_message
-    @error_message || &quot;must be a simple domain.&quot;
+    @error_message || :domain
   end
 
   def validate(value, record)
@@ -17,7 +17,7 @@ class Predicates::Domain &lt; Predicates::Base
     false
   end
 
-  def from_human(v)
+  def normalize(v)
     URI.parse(with_protocol(v)).host || v
   rescue URI::InvalidURIError
     v
@@ -28,4 +28,4 @@ class Predicates::Domain &lt; Predicates::Base
   def with_protocol(value)
     !value or value.include?(&quot;://&quot;) ? value : &quot;http://#{value}&quot;
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/domain.rb</filename>
    </modified>
    <modified>
      <diff>@@ -27,7 +27,7 @@ class Predicates::Email &lt; Predicates::Base
   end
 
   def error_message
-    @error_message || 'must be an email address.'
+    @error_message || :email
   end
 
   EmailAddressPattern = begin
@@ -39,4 +39,4 @@ class Predicates::Email &lt; Predicates::Base
       Regexp::EXTENDED | Regexp::IGNORECASE
     )
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/email.rb</filename>
    </modified>
    <modified>
      <diff>@@ -8,11 +8,16 @@
 class Predicates::Enumerated &lt; Predicates::Base
   attr_accessor :options
 
+  def initialize(attr, options = {})
+    options[:or_empty] ||= false
+    super(attr, options)
+  end
+
   def error_message
-    @error_message || &quot;is not an allowed option.&quot;
+    @error_message || :inclusion
   end
 
   def validate(value, record)
     self.options.include? value
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/enumerated.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,10 +6,10 @@ class Predicates::HexColor &lt; Predicates::Pattern
   end
 
   def error_message
-    @error_message ||= &quot;must be a hex color.&quot;
+    @error_message ||= :hex
   end
 
-  def from_human(value)
+  def normalize(value)
     return value if value.blank?
 
     # ensure leading pound sign
@@ -21,4 +21,4 @@ class Predicates::HexColor &lt; Predicates::Pattern
 
     value
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/hex_color.rb</filename>
    </modified>
    <modified>
      <diff>@@ -24,11 +24,17 @@ class Predicates::Length &lt; Predicates::Base
   attr_accessor :exactly
 
   def error_message
-    @error_message || &quot;must be #{range_description} characters long.&quot;
+    @error_message || range_description
+  end
+  
+  def error_binds
+    self.range ?
+      {:min =&gt; self.range.first, :max =&gt; self.range.last} :
+      {:min =&gt; self.above, :max =&gt; self.below, :count =&gt; self.exactly}
   end
 
   def validate(value, record)
-    l = value.to_s.chars.length
+    l = tokenize(value).length
     if self.exactly
       l == self.exactly
     elsif self.range
@@ -43,12 +49,19 @@ class Predicates::Length &lt; Predicates::Base
   end
 
   protected
-
+  
+  def tokenize(value)
+    case value
+      when Array, Hash: value
+      else              value.to_s.mb_chars
+    end
+  end
+  
   def range_description
-    return self.exactly.to_s if self.exactly
-    return &quot;#{self.range.first} to #{self.range.last}&quot; if self.range
-    return &quot;more than #{self.above}&quot; if self.above
-    return &quot;less than #{self.below}&quot; if self.below
+    return :inexact_length if self.exactly
+    return :wrong_length if self.range
+    return :too_short if self.above
+    return :too_long if self.below
     raise 'undetermined range'
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/length.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,6 +6,8 @@
 # * :below [integer, float] - when the number has a maximum
 # * :range [range] - when the number has a minimum and a maximum
 # * :inclusive [boolean, default: false] - if your maximum or minimum is also an allowed value. Does not work with :range.
+# * :at_least [integer, float] - an easy way to say :above and :inclusive
+# * :no_more_than [integer, float] - an easy way to say :below and :inclusive
 #
 # ==Examples
 #   field_is_a_number :integer =&gt; true
@@ -28,9 +30,25 @@ class Predicates::Number &lt; Predicates::Base
   # meant to be used with :above and :below, when you want the endpoint to be inclusive.
   # with the :range option you can just specify inclusion using the standard Ruby range syntax.
   attr_accessor :inclusive
+  
+  def at_least=(val)
+    self.above = val
+    self.inclusive = true
+  end
+  
+  def no_more_than=(val)
+    self.below = val
+    self.inclusive = true
+  end
 
   def error_message
-    @error_message || &quot;must be a number#{range_description}.&quot;
+    @error_message || range_description
+  end
+  
+  def error_binds
+    self.range ?
+      {:min =&gt; self.range.first, :max =&gt; self.range.last} :
+      {:min =&gt; self.above, :max =&gt; self.below}
   end
 
   def validate(value, record)
@@ -68,16 +86,19 @@ class Predicates::Number &lt; Predicates::Base
 
   def range_description
     # if it has two endpoints
-    return &quot; from #{self.range.first} #{self.range.exclude_end? ? 'to' : 'through'} #{self.range.last}&quot; if self.range
+    if self.range
+      return :between
+    end
     # if it only has one inclusive endpoint
     if inclusive
-      return &quot; at least #{self.above}&quot; if self.above
-      return &quot; no more than #{self.below}&quot; if self.below
+      return :greater_than_or_equal_to if self.above
+      return :less_than_or_equal_to if self.below
     # if it only has one exclusive endpoint
     else
-      return &quot; greater than #{self.above}&quot; if self.above
-      return &quot; less than #{self.below}&quot; if self.below
+      return :greater_than if self.above
+      return :less_than if self.below
     end
     # if it has no endpoints
+    :not_a_number
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/number.rb</filename>
    </modified>
    <modified>
      <diff>@@ -15,12 +15,8 @@
 class Predicates::Pattern &lt; Predicates::Base
   attr_accessor :like
 
-  def error_message
-    @error_message ||= &quot;is invalid.&quot;
-  end
-
   def validate(value, record)
     value = value.to_s if [Symbol, Fixnum].include?(value.class)
     value.match(self.like)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/pattern.rb</filename>
    </modified>
    <modified>
      <diff>@@ -14,7 +14,7 @@ class Predicates::PhoneNumber &lt; Predicates::Base
   end
 
   def error_message
-    @error_message ||= &quot;must be a phone number.&quot;
+    @error_message ||= :phone
   end
 
   def validate(value, record)
@@ -44,7 +44,7 @@ class Predicates::PhoneNumber &lt; Predicates::Base
   end
 
   # strip out all non-numeric characters except a leading +
-  def from_human(value)
+  def normalize(value)
     value = &quot;+#{value}&quot; if value.to_s[0..0] == '1' # north american bias
     value = &quot;+#{implied_country_code}#{value}&quot; unless value.to_s[0..0] == '+'
 
@@ -57,4 +57,4 @@ class Predicates::PhoneNumber &lt; Predicates::Base
   class Patterns
     NANP = /\A\+1([2-9][0-8][0-9])([2-9][0-9]{2})([0-9]{4})\Z/
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/phone_number.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,10 +13,10 @@ class Predicates::Required &lt; Predicates::Base
   end
 
   def error_message
-    @error_message || 'is required.'
+    @error_message || :required
   end
 
   def validate(value, record)
     !value.blank?
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/required.rb</filename>
    </modified>
    <modified>
      <diff>@@ -8,10 +8,10 @@ class Predicates::SameAs &lt; Predicates::Base
   attr_accessor :method
 
   def error_message
-    @error_message ||= &quot;must be the same as #{method}.&quot;
+    @error_message ||= :same_as
   end
-
+  
   def validate(value, record)
     value == record.send(self.method)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/same_as.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,7 @@
 # ==Options
 # * :before [time] - specifies that the value must predate the given time (as object or string)
 # * :after [time] - specifies that the value must postdate the given time (as object or string)
+# * :distance [range] - specifies a range of seconds that provide an minimum and maximum time value to be calculated from the current time.
 class Predicates::Time &lt; Predicates::Base
   # specifies a time that must postdate any valid value
   def before=(val)
@@ -15,16 +16,28 @@ class Predicates::Time &lt; Predicates::Base
     @after = val.is_a?(Time) ? val : Time.parse(val)
   end
   attr_reader :after
+  
+  # specifies a range of seconds where the lower boundary
+  # is the minimum time value and the upper boundary is the
+  # maximum time value, both interpreted from the current
+  # time.
+  attr_accessor :distance
 
   def error_message
-    @error_message || &quot;must be a point in time.&quot;
+    @error_message || :time
   end
 
   def validate(value, record)
     valid = value.is_a? Time
     valid &amp;&amp;= (value &lt; self.before) if self.before
     valid &amp;&amp;= (value &gt; self.after) if self.after
+    
+    if self.distance
+      now = Time.now.utc
+      valid &amp;&amp;= (value &gt;= now + self.distance.begin)
+      valid &amp;&amp;= (value &lt;= now + self.distance.end)
+    end
 
     valid
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/time.rb</filename>
    </modified>
    <modified>
      <diff>@@ -20,30 +20,56 @@ class Predicates::Unique &lt; Predicates::Base
   end
 
   def error_message
-    @error_message || &quot;has already been taken.&quot;
+    @error_message || :taken
   end
 
-  def validate(value, record)
-    fields_values = [[@attribute, value]]
-    [scope].flatten.each { |attribute| fields_values &lt;&lt; [attribute, record.send(attribute)] }
+  def validate(value, record)  
+    klass = record.class
+  
+    # merge all the scope fields with this one. they must all be unique together.
+    # no special treatment -- case sensitivity applies to all or none.
+    values = [scope].flatten.collect{ |attr| [attr, record.send(attr)] }
+    values &lt;&lt; [@attribute, value]
 
-    conditions_array = ['']
-    fields_values.each do |(attribute, attribute_value)|
-      field_sql = &quot;#{record.class.table_name}.#{attribute}&quot;
-      comparison_value = attribute_value
+    conditions_sql = []
+    conditions_params = []
+    values.each do |(attr, attr_value)|
+      field_sql, comparison_value = *comparison_for(attr, attr_value, klass)
+      conditions_sql    &lt;&lt; field_sql
+      conditions_params &lt;&lt; comparison_value
+    end
+    
+    unless record.new_record?
+      conditions_sql    &lt;&lt; &quot;#{klass.quoted_table_name}.#{klass.primary_key} &lt;&gt; ?&quot;
+      conditions_params &lt;&lt; record.id
+    end
 
-      if record.class.columns_hash[attribute.to_s].text? and not self.case_sensitive
-        field_sql = &quot;LOWER(#{field_sql})&quot;
-        comparison_value = comparison_value.downcase unless comparison_value.nil?
+    !klass.exists?([conditions_sql.join(&quot; AND &quot;), *conditions_params])
+  end
+  
+  protected
+  
+  def comparison_for(field, value, klass)
+    quoted_field = &quot;#{klass.quoted_table_name}.#{klass.connection.quote_column_name(field)}&quot;
+  
+    if klass.columns_hash[field.to_s].text?      
+      if case_sensitive
+        # case sensitive text comparison in any database
+        [&quot;#{quoted_field} #{klass.connection.case_sensitive_equality_operator} ?&quot;, value]
+      elsif mysql?(klass.connection)
+        # case INsensitive text comparison in mysql - yes this is a database specific optimization. i'm always open to better ways. :)
+        [&quot;#{quoted_field} = ?&quot;, value]
+      else
+        # case INsensitive text comparison in most databases
+        [&quot;LOWER(#{quoted_field}) = ?&quot;, value.to_s.downcase]
       end
-      field_sql &lt;&lt; ' ' &lt;&lt; record.class.send(:attribute_condition, comparison_value)
-
-      conditions_array.first &lt;&lt; ' AND ' unless conditions_array.first.empty?
-      conditions_array.first &lt;&lt; field_sql
-      conditions_array &lt;&lt; comparison_value
+    else
+      # non-text comparison
+      [klass.send(:attribute_condition, quoted_field, value), value]
     end
-
-    result = record.class.find(:all, :conditions =&gt; conditions_array, :limit =&gt; 2)
-    return (result.size &lt; 1 or result.first == record)
   end
-end
\ No newline at end of file
+  
+  def mysql?(connection)
+    defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter) and connection.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
+  end
+end</diff>
      <filename>lib/predicates/unique.rb</filename>
    </modified>
    <modified>
      <diff>@@ -33,7 +33,7 @@ class Predicates::Url &lt; Predicates::Base
   end
 
   def error_message
-    @error_message || 'must be a valid URL.'
+    @error_message || :url
   end
 
   def validate(value, record)
@@ -52,11 +52,11 @@ class Predicates::Url &lt; Predicates::Base
     false
   end
 
-  def from_human(v)
+  def normalize(v)
     url = URI.parse(v)
     url = URI.parse(&quot;#{self.implied_scheme}://#{v}&quot;) if self.implied_scheme and not (url.scheme and url.host)
     url.to_s
   rescue URI::InvalidURIError
     v
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/url.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@ class Predicates::UsaState &lt; Predicates::Aliased
   undef_method :options=
 
   def error_message
-    @error_message || &quot;must be a US state#{' or territory' if with_territories?}.&quot;
+    @error_message || with_territories? ? :us_state_or_territory : :us_state
   end
 
   TERRITORIES = {
@@ -84,4 +84,4 @@ class Predicates::UsaState &lt; Predicates::Aliased
     'Wisconsin' =&gt; 'WI',
     'Wyoming' =&gt; 'WY'
   }
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/usa_state.rb</filename>
    </modified>
    <modified>
      <diff>@@ -20,6 +20,6 @@ class Predicates::UsaZipCode &lt; Predicates::Pattern
   undef_method :like=
 
   def error_message
-    @error_message || 'must be a US zip code.'
+    @error_message || :us_zip_code
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/predicates/usa_zip_code.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,31 +1,19 @@
 module SemanticAttributes #:nodoc:
-  # Some predicates define different data formats for machines vs humans. For example, your code might always want phone numbers to be integers, but that's not so readable for humans.
-  #
-  # SemanticAttributes uses the following pattern to handle the different formats:
-  #
-  # user#phone_number::                 get machine format [normal behavior]
-  # user#phone_number_for_human::          get human format [new]
-  # user#phone_number=::                   set from possibly-human value [override wrapper]
-  #
-  # These patterns are supported by the following helpers:
-  #
-  # User.humanize(attribute, object)::     where 'object' is a string, integer, date, whatever
-  # User.machinize(attribute, object)::    where 'object' is a simple string, array, or hash, as from form parameters
-  #
   module AttributeFormats
     def self.included(base)
       base.extend ClassMethods
-
-      # this method is private
-      base.class_eval do
-        def write_attribute_with_formats(attr, value)
-          value = self.class.machinize(attr, value) if semantic_attributes and semantic_attributes.include? attr
-          write_attribute_without_formats attr, value
-        end
-        alias_method_chain :write_attribute, :formats
+    end
+    
+    def respond_to?(method_name, *args)
+      if md = method_name.to_s.match(/_for_human$/) and semantic_attributes.include?(md.pre_match)
+        true
+      else
+        super
       end
     end
-
+    
+    protected
+    
     def method_missing(method_name, *args, &amp;block)
       if md = method_name.to_s.match(/_for_human$/) and semantic_attributes.include?(md.pre_match)
         self.class.humanize(md.pre_match, self.send(md.pre_match))
@@ -38,14 +26,6 @@ module SemanticAttributes #:nodoc:
       end
     end
 
-    def respond_to?(method_name, *args)
-      if md = method_name.to_s.match(/_for_human$/) and semantic_attributes.include?(md.pre_match)
-        true
-      else
-        super
-      end
-    end
-
     module ClassMethods
       # converts the object into human format according to the predicates for the attribute.
       # the object may really be anything: string, integer, date, etc.
@@ -55,9 +35,28 @@ module SemanticAttributes #:nodoc:
 
       # converts the object into machine format according to the predicates for the attribute.
       # the object should be a simple object like a string, array, or hash. but really, it depends on the the predicates.
-      def machinize(attr, obj)
-        self.semantic_attributes[attr].predicates.inject(obj) { |val, predicate| val = predicate.from_human(val) }
+      def normalize(attr, obj)
+        self.semantic_attributes[attr].predicates.inject(obj) { |val, predicate| val = predicate.normalize(val) }
       end
+      
+      protected
+
+      def define_normalization_method_for(attr)
+        self.define_attribute_methods if self.respond_to? :generated_methods? and !self.generated_methods?
+        
+        writer = &quot;#{attr}_with_normalization=&quot;
+        old_writer = &quot;#{attr}_without_normalization=&quot;.to_sym
+        unless instance_methods.include? writer
+          define_method writer do |val|
+            send(old_writer, self.class.normalize(attr, val))
+          end
+          alias_method_chain &quot;#{attr}=&quot;, :normalization
+        end
+      rescue NameError
+        # so the top line of the backtrace shows the proper method
+        raise NameError, $!.message, caller[4..-1]
+      end
+
     end
   end
 end</diff>
      <filename>lib/semantic_attributes/attribute_formats.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,18 +18,20 @@ module SemanticAttributes
     # the validation hook that checks all predicates
     def validate_predicates
       semantic_attributes.each do |attribute|
-        attribute.predicates.each do |predicate|
-          next unless validate_predicate?(predicate)
-
-          value = self.send(attribute.field)
+        applicable_predicates = attribute.predicates.select{|p| validate_predicate?(p)}
+        
+        next if applicable_predicates.empty?
+        
+        value = self.send(attribute.field)
+        applicable_predicates.each do |predicate|
           if value.blank?
             # it's empty, so add an error or not but either way move along
-            self.errors.add(attribute.field, _(predicate.error_message)) unless predicate.allow_empty?
+            self.errors.add(attribute.field, predicate.error) unless predicate.allow_empty?
             next
           end
 
           unless predicate.validate(value, self)
-            self.errors.add(attribute.field, _(predicate.error_message))
+            self.errors.add(attribute.field, predicate.error)
           end
         end
       end
@@ -95,16 +97,10 @@ module SemanticAttributes
         begin
           super
         rescue NameError
-          if /^(.*)_(is|has)_(an?_)?(required_)?([^?]*)(\?)?$/.match(name.to_s)
+          if /^(.*)_(is|has|are)_(an?_)?(required_)?([^?]*)(\?)?$/.match(name.to_s)
             options = args.last.is_a?(Hash) ? args.pop : {}
             options[:or_empty] = false if !$4.nil?
-            fields = ($1 == 'fields') ? args : [$1]
-            
-            fields.each do |f|
-              unless instance_methods.include?(f) or column_names.include?(f)
-                raise ArgumentError.new(&quot;unknown attribute `#{f}'&quot;)
-              end
-            end
+            fields = ($1 == 'fields') ? args.map(&amp;:to_s) : [$1]
             
             predicate = $5
             if $6 == '?'
@@ -113,6 +109,8 @@ module SemanticAttributes
               args = [predicate]
               args &lt;&lt; options if options
               fields.each do |field|
+                # TODO: create a less sugary method that may be used programmatically and takes care of defining the normalization method properly
+                define_normalization_method_for field
                 self.semantic_attributes[field].add *args
               end
             end</diff>
      <filename>lib/semantic_attributes/predicates.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,7 @@ ENV[&quot;RAILS_ENV&quot;] = &quot;test&quot;
 # load the support libraries
 require 'test/unit'
 require 'rubygems'
+gem 'rails', '2.3.2'
 require 'active_record'
 require 'active_record/fixtures'
 require 'mocha'
@@ -27,7 +28,8 @@ load(File.dirname(__FILE__) + &quot;/db/schema.rb&quot;)
 require File.dirname(__FILE__) + '/db/models'
 
 # configure the TestCase settings
-class Test::Unit::TestCase
+class SemanticAttributes::TestCase &lt; ActiveSupport::TestCase
+  include ActiveRecord::TestFixtures
   include PluginTestModels
 
   self.use_transactional_fixtures = true</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,8 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class ActiveRecordExtensionsTest &lt; Test::Unit::TestCase
+class ActiveRecordExtensionsTest &lt; SemanticAttributes::TestCase
   class FooUser &lt; User
-    attr_reader :foo
+    attr_accessor :foo
   end
   
   def setup
@@ -43,10 +43,18 @@ class ActiveRecordExtensionsTest &lt; Test::Unit::TestCase
   
   def test_declarative_sugar_catches_unknown_attributes
     @klass = FooUser
-    assert_raises ArgumentError do
+    assert_raises NameError do
       @klass.unknown_is_required
     end
   end
+  
+  def test_declarative_sugar_for_mass_assignment
+    @klass = FooUser
+    assert_nothing_raised do
+      @klass.fields_are_required :foo
+    end
+    assert @klass.foo_is_required?
+  end
 
   def test_method_missing_still_works
     assert_raise NoMethodError do User.i_do_not_exist end</diff>
      <filename>test/unit/active_record_predicates_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,11 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class AttributeFormatsTest &lt; Test::Unit::TestCase
+class AttributeFormatsTest &lt; SemanticAttributes::TestCase
+  class User &lt; User
+    cell_is_a_phone_number
+  end
+
   def setup
-    User.stub_semantics_with(:cell =&gt; :phone_number)
     @record = User.new
   end
 
@@ -10,7 +13,7 @@ class AttributeFormatsTest &lt; Test::Unit::TestCase
     assert_nothing_raised do
       @record.cell = '(222) 333.4444'
     end
-    assert_equal '+12223334444', @record.attributes['cell'], 'value is stored in machinized format'
+    assert_equal '+12223334444', @record.attributes['cell'], 'value is stored in normalized format'
 
     assert_equal '+12223334444', @record.cell, 'read defaults to machine format'
     assert @record.respond_to?(:cell_for_human)
@@ -26,11 +29,11 @@ class AttributeFormatsTest &lt; Test::Unit::TestCase
     assert_raise NoMethodError do @record.login_for_human end
   end
 
-  def test_machinize
-    assert_equal '+12223334444', User.machinize(:cell, '(222) 333.4444')
+  def test_normalize
+    assert_equal '+12223334444', User.normalize(:cell, '(222) 333.4444')
   end
 
   def test_humanize
     assert_equal '(222) 333-4444', User.humanize(:cell, '+12223334444')
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/attribute_formats_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,13 +1,13 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class InheritanceTest &lt; Test::Unit::TestCase
+class InheritanceTest &lt; SemanticAttributes::TestCase
   class Worker &lt; User
-    attr_reader :name_tag
+    attr_accessor :name_tag
     name_tag_is_required
   end
 
   class Clerk &lt; Worker
-    attr_reader :pen
+    attr_accessor :pen
     pen_is_required
   end
 </diff>
      <filename>test/unit/inheritance_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,17 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class AliasedPredicateTest &lt; Test::Unit::TestCase
+class AliasedPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Aliased.new(:foo, :options =&gt; {'10111001' =&gt; '185'})
   end
 
   def test_formats
     assert_equal '185', @predicate.to_human('10111001')
-    assert_equal '10111001', @predicate.from_human('185')
+    assert_equal '10111001', @predicate.normalize('185')
+  end
+
+  def test_validation
+    assert @predicate.validate('185', nil)
+    assert !@predicate.validate('10111001', nil)
   end
 end</diff>
      <filename>test/unit/predicates/aliased_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class AssociationPredicateTest &lt; Test::Unit::TestCase
+class AssociationPredicateTest &lt; SemanticAttributes::TestCase
   def test_singular_associations
     predicate = Predicates::Association.new(:subscription)
     assert predicate.validate(users(:bob).subscription, users(:bob))</diff>
      <filename>test/unit/predicates/association_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,12 +1,23 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class BlacklistedPredicateTest &lt; Test::Unit::TestCase
+class BlacklistedPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Blacklisted.new(:foo)
   end
 
-  def test_validation
-    @predicate.options = [1, 2, '3']
+  def test_case_insensitive_validation
+    @predicate.case_sensitive = false
+    @predicate.restricted = [1, &quot;foo&quot;]
+    assert !@predicate.validate(1, nil)
+    assert !@predicate.validate(&quot;1&quot;, nil)
+    assert !@predicate.validate(&quot;foo&quot;, nil)
+    assert !@predicate.validate(&quot;foO&quot;, nil)
+  end
+  
+  def test_case_sensitive_validation
+    @predicate.case_sensitive = true
+  
+    @predicate.restricted = [1, 2, '3']
 
     assert !@predicate.validate(1, nil)
     assert !@predicate.validate(2, nil)</diff>
      <filename>test/unit/predicates/blacklisted_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,16 +1,10 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class DomainPredicateTest &lt; Test::Unit::TestCase
+class DomainPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Domain.new(:foo)
   end
 
-  def test_error_message
-    assert_equal 'must be a simple domain.', @predicate.error_message
-    @predicate.error_message = 'foo'
-    assert_equal 'foo', @predicate.error_message
-  end
-
   def test_valid_domains
     %w(example.com www.example.com).each do |domain|
       assert @predicate.validate(domain, nil), &quot;#{domain} is a valid domain&quot;
@@ -23,11 +17,11 @@ class DomainPredicateTest &lt; Test::Unit::TestCase
     end
   end
 
-  def test_from_human
-    assert_equal &quot;example.com&quot;, @predicate.from_human(&quot;http://example.com:8080/foo&quot;)
-    assert_equal &quot;example.com&quot;, @predicate.from_human(&quot;example.com/foo&quot;)
-    assert_equal nil, @predicate.from_human(nil)
-    assert_equal &quot;&quot;, @predicate.from_human(&quot;&quot;)
-    assert_equal &quot;example.com&quot;, @predicate.from_human(&quot;example.com&quot;)
+  def test_normalize
+    assert_equal &quot;example.com&quot;, @predicate.normalize(&quot;http://example.com:8080/foo&quot;)
+    assert_equal &quot;example.com&quot;, @predicate.normalize(&quot;example.com/foo&quot;)
+    assert_equal nil, @predicate.normalize(nil)
+    assert_equal &quot;&quot;, @predicate.normalize(&quot;&quot;)
+    assert_equal &quot;example.com&quot;, @predicate.normalize(&quot;example.com&quot;)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/domain_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,7 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 require 'actionmailer'
 
-class EmailPredicateTest &lt; Test::Unit::TestCase
+class EmailPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Email.new(:foo)
   end
@@ -80,10 +80,4 @@ class EmailPredicateTest &lt; Test::Unit::TestCase
     assert !@predicate.validate('test@example.com', nil), 'syntax and mx check'
     assert @predicate.validate('test@gmail.com', nil)
   end
-
-  def test_error_message
-    assert_equal 'must be an email address.', @predicate.error_message
-    @predicate.error_message = 'foo'
-    assert_equal 'foo', @predicate.error_message
-  end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/email_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class EnumeratedPredicateTest &lt; Test::Unit::TestCase
+class EnumeratedPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Enumerated.new(:foo)
   end
@@ -15,9 +15,8 @@ class EnumeratedPredicateTest &lt; Test::Unit::TestCase
     assert !@predicate.validate(6, nil)
   end
 
-  def test_error_message
-    assert_equal &quot;is not an allowed option.&quot;, @predicate.error_message
-    @predicate.error_message = 'foo'
-    assert_equal 'foo', @predicate.error_message
+  def test_or_empty
+    assert !Predicates::Enumerated.new(:foo).allow_empty?
+    assert Predicates::Enumerated.new(:foo, :or_empty =&gt; true).allow_empty?
   end
 end</diff>
      <filename>test/unit/predicates/enumerated_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,21 +1,17 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class HexColorPredicateTest &lt; Test::Unit::TestCase
+class HexColorPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::HexColor.new(:foo)
   end
 
-  def test_default_error_message
-    assert_equal @predicate.error_message, &quot;must be a hex color.&quot;
-  end
-
-  def test_from_human_conversions
-    assert_equal &quot;&quot;, @predicate.from_human(&quot;&quot;)
-    assert_equal nil, @predicate.from_human(nil)
-    assert_equal &quot;#123456&quot;, @predicate.from_human(&quot;123456&quot;), &quot;adds a pound sign&quot;
-    assert_equal &quot;#123456&quot;, @predicate.from_human(&quot;#123456&quot;), &quot;does not duplicate the pound sign&quot;
-    assert_equal &quot;#112233&quot;, @predicate.from_human(&quot;123&quot;), &quot;expand three-character syntax&quot;
-    assert_equal &quot;#112233&quot;, @predicate.from_human(&quot;#123&quot;), &quot;handles existing pound sign when expanding to six characters&quot;
+  def test_normalize_conversions
+    assert_equal &quot;&quot;, @predicate.normalize(&quot;&quot;)
+    assert_equal nil, @predicate.normalize(nil)
+    assert_equal &quot;#123456&quot;, @predicate.normalize(&quot;123456&quot;), &quot;adds a pound sign&quot;
+    assert_equal &quot;#123456&quot;, @predicate.normalize(&quot;#123456&quot;), &quot;does not duplicate the pound sign&quot;
+    assert_equal &quot;#112233&quot;, @predicate.normalize(&quot;123&quot;), &quot;expand three-character syntax&quot;
+    assert_equal &quot;#112233&quot;, @predicate.normalize(&quot;#123&quot;), &quot;handles existing pound sign when expanding to six characters&quot;
   end
 
   def test_valid_colors
@@ -30,4 +26,4 @@ class HexColorPredicateTest &lt; Test::Unit::TestCase
     end
   end
 
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/hex_color_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,13 +1,13 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class LengthPredicateTest &lt; Test::Unit::TestCase
+class LengthPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Length.new(:foo)
   end
 
   def test_range
     @predicate.range = 1..5
-    assert_equal 'must be 1 to 5 characters long.', @predicate.error_message
+    assert_equal :wrong_length, @predicate.error_message
 
     assert !@predicate.validate('', nil)
     assert @predicate.validate('1', nil)
@@ -15,13 +15,13 @@ class LengthPredicateTest &lt; Test::Unit::TestCase
     assert !@predicate.validate('123456', nil)
 
     @predicate.range = 1...5
-    assert_equal 'must be 1 to 5 characters long.', @predicate.error_message, 'same message for inclusive range'
+    assert_equal :wrong_length, @predicate.error_message, 'same message for inclusive range'
     assert !@predicate.validate('12345', nil)
   end
 
   def test_min
     @predicate.above = 5
-    assert_equal 'must be more than 5 characters long.', @predicate.error_message
+    assert_equal :too_short, @predicate.error_message
 
     assert !@predicate.validate('', nil)
     assert !@predicate.validate('12345', nil)
@@ -30,7 +30,7 @@ class LengthPredicateTest &lt; Test::Unit::TestCase
 
   def test_max
     @predicate.below = 5
-    assert_equal 'must be less than 5 characters long.', @predicate.error_message
+    assert_equal :too_long, @predicate.error_message
 
     assert @predicate.validate('', nil)
     assert @predicate.validate('1234', nil)
@@ -39,23 +39,41 @@ class LengthPredicateTest &lt; Test::Unit::TestCase
 
   def test_exact
     @predicate.exactly = 5
-    assert_equal 'must be 5 characters long.', @predicate.error_message
+    assert_equal :inexact_length, @predicate.error_message
 
     assert !@predicate.validate('', nil)
     assert !@predicate.validate('1234', nil)
     assert @predicate.validate('12345', nil)
     assert !@predicate.validate('123456', nil)
   end
-
-  def test_data_types
-    @predicate.range = 1..5
+  
+  def test_symbols
+    @predicate.range = 2..3
     assert @predicate.validate(:abc, nil)
-    assert @predicate.validate(6, nil), 'length converts to a string'
-    assert @predicate.validate(0, nil), 'length converts to a string'
+    assert !@predicate.validate(:abcdef, nil)
+  end
+  
+  def test_numbers
+    @predicate.range = 2..3
+    assert !@predicate.validate(3, nil)
+    assert @predicate.validate(123, nil)
+  end
+  
+  def test_arrays
+    @predicate.range = 2..3
+    assert !@predicate.validate(['abc'], nil)
+    assert @predicate.validate(['abc', 'def', 'ghi'], nil)
+  end
+  
+  def test_multibyte_characters
+    $KCODE = &quot;UTF8&quot; # so ActiveSupport uses the UTF8Handler for Chars
+    @predicate.exactly = 4
+    assert @predicate.validate('&#230;gis', nil)
+    assert !@predicate.validate('aegis', nil)
   end
 
   def test_no_options
     assert_raise RuntimeError do @predicate.error_message end
     assert @predicate.validate(nil, nil)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/length_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,14 +1,12 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class NumberPredicateTest &lt; Test::Unit::TestCase
+class NumberPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Number.new(:foo)
   end
 
   def test_basic_error_message
-    assert_equal 'must be a number.', @predicate.error_message
-    @predicate.error_message = 'foo'
-    assert_equal 'foo', @predicate.error_message
+    assert_equal :not_a_number, @predicate.error_message
   end
 
   def test_integers
@@ -38,7 +36,7 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
 
   def test_min
     @predicate.above = 5
-    assert_equal 'must be a number greater than 5.', @predicate.error_message
+    assert_equal :greater_than, @predicate.error_message
 
     assert !@predicate.validate(-10, nil)
     assert !@predicate.validate(-5, nil)
@@ -52,9 +50,10 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
   end
 
   def test_min_inclusive
-    @predicate.above = 5
-    @predicate.inclusive = true
-    assert_equal 'must be a number at least 5.', @predicate.error_message
+    @predicate.at_least = 5
+    assert_equal 5, @predicate.above
+    assert @predicate.inclusive
+    assert_equal :greater_than_or_equal_to, @predicate.error_message
 
     assert !@predicate.validate(4, nil)
     assert @predicate.validate(5, nil)
@@ -62,7 +61,7 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
 
   def test_max
     @predicate.below = 5
-    assert_equal 'must be a number less than 5.', @predicate.error_message
+    assert_equal :less_than, @predicate.error_message
 
     assert @predicate.validate(-10, nil)
     assert @predicate.validate(-5, nil)
@@ -76,9 +75,10 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
   end
 
   def test_max_inclusive
-    @predicate.below = 5
-    @predicate.inclusive = true
-    assert_equal 'must be a number no more than 5.', @predicate.error_message
+    @predicate.no_more_than = 5
+    assert_equal 5, @predicate.below
+    assert @predicate.inclusive
+    assert_equal :less_than_or_equal_to, @predicate.error_message
 
     assert @predicate.validate(5, nil)
     assert !@predicate.validate(6, nil)
@@ -86,7 +86,7 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
 
   def test_range
     @predicate.range = -5...5
-    assert_equal 'must be a number from -5 to 5.', @predicate.error_message
+    assert_equal :between, @predicate.error_message
 
     assert !@predicate.validate(-10, nil)
     assert @predicate.validate(-5, nil)
@@ -101,9 +101,9 @@ class NumberPredicateTest &lt; Test::Unit::TestCase
 
   def test_range_inclusive
     @predicate.range = -5..5
-    assert_equal 'must be a number from -5 through 5.', @predicate.error_message
+    assert_equal :between, @predicate.error_message
 
     assert @predicate.validate(5, nil)
     assert !@predicate.validate(6, nil)
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/number_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class PatternPredicateTest &lt; Test::Unit::TestCase
+class PatternPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Pattern.new(:foo)
   end</diff>
      <filename>test/unit/predicates/pattern_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class PhoneNumberPredicateTest &lt; Test::Unit::TestCase
+class PhoneNumberPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::PhoneNumber.new(:foo)
   end
@@ -28,13 +28,13 @@ class PhoneNumberPredicateTest &lt; Test::Unit::TestCase
     assert @predicate.validate('+12225550200', nil), 'allowed 555 code'
   end
 
-  def test_from_human
+  def test_normalize
     @predicate.implied_country_code = 99
 
-    assert_equal '+12223334444', @predicate.from_human('12223334444'), 'recognizes north american country code without +'
-    assert_equal '+12223334444', @predicate.from_human('+12223334444'), 'leaves country codes alone if they exist'
-    assert_equal '+992223334444', @predicate.from_human('2223334444'), 'adds implied country code'
-    assert_equal '+12223334444', @predicate.from_human('1 (222) 333.4444'), 'ignores various formatting characters'
-    assert_equal '+992223334444', @predicate.from_human('222typo333oops4444'), 'ignores non-numeric characters'
+    assert_equal '+12223334444', @predicate.normalize('12223334444'), 'recognizes north american country code without +'
+    assert_equal '+12223334444', @predicate.normalize('+12223334444'), 'leaves country codes alone if they exist'
+    assert_equal '+992223334444', @predicate.normalize('2223334444'), 'adds implied country code'
+    assert_equal '+12223334444', @predicate.normalize('1 (222) 333.4444'), 'ignores various formatting characters'
+    assert_equal '+992223334444', @predicate.normalize('222typo333oops4444'), 'ignores non-numeric characters'
   end
 end
\ No newline at end of file</diff>
      <filename>test/unit/predicates/phone_number_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class RequiredPredicateTest &lt; Test::Unit::TestCase
+class RequiredPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Required.new(:foo)
   end</diff>
      <filename>test/unit/predicates/required_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class SameAsPredicateTest &lt; Test::Unit::TestCase
+class SameAsPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::SameAs.new(:foo)
   end</diff>
      <filename>test/unit/predicates/same_as_predicate_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,16 +1,10 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class TimePredicateTest &lt; Test::Unit::TestCase
+class TimePredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Time.new(:foo)
   end
 
-  def test_error_message
-    assert_equal 'must be a point in time.', @predicate.error_message
-    @predicate.error_message = 'foo'
-    assert_equal 'foo', @predicate.error_message
-  end
-
   def test_default_validation
     assert !@predicate.validate('2007-07-19 00:00:00', nil), 'value may not be a string, even if string parses'
     assert !@predicate.validate(1184817600, nil), 'value must not be a timestamp'
@@ -43,4 +37,13 @@ class TimePredicateTest &lt; Test::Unit::TestCase
     assert @predicate.validate(Time.parse('2006-06-01 00:00:00'), nil), 'may be inbetween'
     assert !@predicate.validate(Time.parse('2008-01-01 00:00:00'), nil), 'may not be after'
   end
-end
\ No newline at end of file
+  
+  def test_validation_with_distance
+    @predicate.distance = (-1.hour)..(1.hour)
+    assert !@predicate.validate(61.minutes.ago, nil)
+    assert @predicate.validate(59.minutes.ago, nil)
+    assert @predicate.validate(Time.now, nil)
+    assert @predicate.validate(Time.now + 59.minutes, nil)
+    assert !@predicate.validate(Time.now + 61.minutes, nil)
+  end
+end</diff>
      <filename>test/unit/predicates/time_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class UniquePredicateTest &lt; Test::Unit::TestCase
+class UniquePredicateTest &lt; SemanticAttributes::TestCase
   def setup
     User.stub_semantics_with(:login =&gt; :unique)
     @predicate = User.semantic_attributes[:login].get(:unique)
@@ -11,7 +11,6 @@ class UniquePredicateTest &lt; Test::Unit::TestCase
   def test_defaults
     assert_equal false, @predicate.case_sensitive, 'case insensitive by default'
     assert_equal [], @predicate.scope, 'default scope is empty array'
-    assert_equal &quot;has already been taken.&quot;, @predicate.error_message
   end
 
   def test_uniqueness_validation_scoping
@@ -56,4 +55,4 @@ class UniquePredicateTest &lt; Test::Unit::TestCase
     @predicate.validate(@fred.login, @fred)
     assert_equal &quot;Fred&quot;, @fred.login, &quot;attribute value was not changed by validate()&quot;
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/unique_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class UrlPredicateTest &lt; Test::Unit::TestCase
+class UrlPredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::Url.new(:foo)
   end
@@ -65,28 +65,22 @@ class UrlPredicateTest &lt; Test::Unit::TestCase
     assert !@predicate.validate('http:\\\\example.com\\', nil)
     assert !@predicate.validate('example.com', nil), 'human format does not validate'
 
-    assert_equal 'http:\\\\example.com\\', @predicate.from_human('http:\\\\example.com\\'), 'malformed human format is preserved'
+    assert_equal 'http:\\\\example.com\\', @predicate.normalize('http:\\\\example.com\\'), 'malformed human format is preserved'
   end
 
   def test_implied_scheme
     assert_equal 'http', @predicate.implied_scheme
 
-    assert_equal 'http://example.com/', @predicate.from_human('http://example.com/'), 'no changes'
-    assert_equal 'ftp://example.com/', @predicate.from_human('ftp://example.com/'), 'no changes when scheme is not default'
-    assert_equal 'http://example.com', @predicate.from_human('example.com'), 'basic implied scheme support'
-    assert_equal 'http://example.com:443', @predicate.from_human('example.com:443'), 'preserve ports'
+    assert_equal 'http://example.com/', @predicate.normalize('http://example.com/'), 'no changes'
+    assert_equal 'ftp://example.com/', @predicate.normalize('ftp://example.com/'), 'no changes when scheme is not default'
+    assert_equal 'http://example.com', @predicate.normalize('example.com'), 'basic implied scheme support'
+    assert_equal 'http://example.com:443', @predicate.normalize('example.com:443'), 'preserve ports'
 
     @predicate.implied_scheme = nil
 
-    assert_equal 'http://example.com/', @predicate.from_human('http://example.com/')
-    assert_equal 'ftp://example.com/', @predicate.from_human('ftp://example.com/')
-    assert_equal 'example.com', @predicate.from_human('example.com')
-    assert_equal 'example.com:80', @predicate.from_human('example.com:80')
+    assert_equal 'http://example.com/', @predicate.normalize('http://example.com/')
+    assert_equal 'ftp://example.com/', @predicate.normalize('ftp://example.com/')
+    assert_equal 'example.com', @predicate.normalize('example.com')
+    assert_equal 'example.com:80', @predicate.normalize('example.com:80')
   end
-
-  def test_error_message
-    assert_equal 'must be a valid URL.', @predicate.error_message
-    @predicate.error_message = 'foo  bar'
-    assert_equal 'foo  bar', @predicate.error_message
-  end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/url_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class UsaStatePredicateTest &lt; Test::Unit::TestCase
+class UsaStatePredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::UsaState.new(:foo)
   end
@@ -11,7 +11,7 @@ class UsaStatePredicateTest &lt; Test::Unit::TestCase
 
   def test_with_territories
     @predicate.with_territories = true
-    assert_equal 'must be a US state or territory.', @predicate.error_message
+    assert_equal :us_state_or_territory, @predicate.error_message
 
     assert @predicate.options.include?('Guam')
     assert @predicate.options.include?('Minnesota')
@@ -19,7 +19,7 @@ class UsaStatePredicateTest &lt; Test::Unit::TestCase
 
   def test_without_territories
     @predicate.with_territories = false
-    assert_equal 'must be a US state.', @predicate.error_message
+    assert_equal :us_state, @predicate.error_message
 
     assert !@predicate.options.include?('Guam')
     assert @predicate.options.include?('Minnesota')</diff>
      <filename>test/unit/predicates/usa_state_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
 
-class UsaZipCodePredicateTest &lt; Test::Unit::TestCase
+class UsaZipCodePredicateTest &lt; SemanticAttributes::TestCase
   def setup
     @predicate = Predicates::UsaZipCode.new(:foo)
   end
@@ -38,6 +38,5 @@ class UsaZipCodePredicateTest &lt; Test::Unit::TestCase
 
   def test_defaults
     assert !@predicate.extended
-    assert_equal 'must be a US zip code.', @predicate.error_message
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/predicates/usa_zip_code_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class SemanticAttributeTest &lt; Test::Unit::TestCase
+class SemanticAttributeTest &lt; SemanticAttributes::TestCase
   def test_everything
     @field = SemanticAttributes::Attribute.new('a')
     assert_equal :a, @field.field</diff>
      <filename>test/unit/semantic_attribute_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class SemanticAttributesTest &lt; Test::Unit::TestCase
+class SemanticAttributesTest &lt; SemanticAttributes::TestCase
   def setup
     @set = SemanticAttributes::Set.new
   end</diff>
      <filename>test/unit/semantic_attributes_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
 
-class ValidationsTest &lt; Test::Unit::TestCase
+class ValidationsTest &lt; SemanticAttributes::TestCase
   def setup
     User.stub_semantics_with(:login =&gt; :required)
     @record = User.new
@@ -18,12 +18,6 @@ class ValidationsTest &lt; Test::Unit::TestCase
     assert_equal 'is required.', @record.errors[:login]
   end
 
-  def test_that_errors_are_localized
-    @record.expects(:_).returns(&quot;translated!&quot;)
-    @record.valid?
-    assert_equal &quot;translated!&quot;, @record.errors[:login]
-  end
-
   def test_validate_on_default
     assert_equal :both, @record.semantic_attributes['login'].get('required').validate_on
   end
@@ -124,4 +118,4 @@ class ValidationsTest &lt; Test::Unit::TestCase
       assert @record.errors.on(:login)
     end
   end
-end
\ No newline at end of file
+end</diff>
      <filename>test/unit/validations_test.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>test/unit/base_predicate_test.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>4684cc8bf95bc059afe584c8094b22802dfd6cee</id>
    </parent>
    <parent>
      <id>6103e695ef47b625bdc81634dbc1d914cf28b6ff</id>
    </parent>
  </parents>
  <author>
    <name>Edwin Moss</name>
    <email>edwin.moss@gmail.com</email>
  </author>
  <url>http://github.com/edwinmoss/semantic-attributes/commit/325739ff1bcf72962a139f2578b8e3835f426907</url>
  <id>325739ff1bcf72962a139f2578b8e3835f426907</id>
  <committed-date>2009-06-25T12:02:16-07:00</committed-date>
  <authored-date>2009-06-25T12:02:16-07:00</authored-date>
  <message>Merge branch 'master' of git://github.com/cainlevy/semantic-attributes into coreteam

Conflicts:
	init.rb
	lib/predicates/aliased.rb
	lib/predicates/usa_state.rb
	lib/semantic_attributes/attribute_formats.rb
	test/unit/predicates/aliased_test.rb</message>
  <tree>649fda357fa6a6d4a02d2e698c0e99c9eb524fe9</tree>
  <committer>
    <name>Edwin Moss</name>
    <email>edwin.moss@gmail.com</email>
  </committer>
</commit>
