aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
committerPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
commitbae00bb1cc392e1cf408369809b9cf85468bef42 (patch)
tree17103af6eeb5de96c72beda1debce28950cc7fea /activerecord/lib
parent93c76b2fb08668bc4b8364cc8051476e6d1d15ba (diff)
parentffd2cf167040b60c26d97c01598560c87bd4b2d3 (diff)
downloadrails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.gz
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.bz2
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.zip
Merge commit 'mainstream/master'
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb31
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb147
-rwxr-xr-xactiverecord/lib/active_record/base.rb28
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb44
-rw-r--r--activerecord/lib/active_record/fixtures.rb16
-rw-r--r--activerecord/lib/active_record/locale/en.yml1
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb80
-rw-r--r--activerecord/lib/active_record/serializers/json_serializer.rb14
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/active_record/validator.rb68
27 files changed, 317 insertions, 303 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 68b0251982..2f1e7573d8 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -69,6 +69,7 @@ module ActiveRecord
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
+ autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
module AttributeMethods
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7f299b2aa5..f494e38e2f 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -42,11 +42,12 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -416,6 +417,32 @@ module ActiveRecord
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
+ # Similarly you can go through a +has_one+ association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, :through => :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
+ # *read-only*. For example, the following would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@@ -819,7 +846,7 @@ module ActiveRecord
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index e67ccfb228..1b7bf42b91 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -208,6 +208,7 @@ module ActiveRecord
# Note that this method will _always_ remove records from the database
# ignoring the +:dependent+ option.
def destroy(*records)
+ records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
remove_records(records) do |records, old_records|
old_records.each { |record| record.destroy }
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index fd23e59e82..d91c555dad 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -1,6 +1,11 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
+ def initialize(owner, reflection)
+ super
+ @primary_key_list = {}
+ end
+
def create(attributes = {})
create_record(attributes) { |record| insert_record(record) }
end
@@ -17,6 +22,12 @@ module ActiveRecord
@reflection.reset_column_information
end
+ def has_primary_key?
+ return @has_primary_key unless @has_primary_key.nil?
+ @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
+ ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
+ end
+
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@@ -29,6 +40,11 @@ module ActiveRecord
end
def insert_record(record, force = true, validate = true)
+ if has_primary_key?
+ raise ActiveRecord::ConfigurationError,
+ "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
+ end
+
if record.new_record?
if force
record.save!
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index e4b631bc54..73d3c23cd3 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -74,6 +74,7 @@ module ActiveRecord
"#{@reflection.primary_key_name} = NULL",
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
+ @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index f4507c979c..829f0ac0c5 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -8,21 +8,11 @@ module ActiveRecord
alias_method :new, :build
def create!(attrs = nil)
- ensure_owner_is_not_new
-
- transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
- object
- end
+ create_record(attrs, true)
end
def create(attrs = nil)
- ensure_owner_is_not_new
-
- transaction do
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
- object
- end
+ create_record(attrs, false)
end
def destroy(*records)
@@ -40,8 +30,18 @@ module ActiveRecord
return @target.size if loaded?
return count
end
-
+
protected
+ def create_record(attrs, force = true)
+ ensure_owner_is_not_new
+
+ transaction do
+ object = @reflection.klass.new(attrs)
+ add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) }
+ object
+ end
+ end
+
def target_reflection_has_associated_record?
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
false
@@ -65,9 +65,10 @@ module ActiveRecord
return false unless record.save(validate)
end
end
- through_reflection = @reflection.through_reflection
- klass = through_reflection.klass
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
+
+ through_association = @owner.send(@reflection.through_reflection.name)
+ through_record = through_association.create!(construct_join_attributes(record))
+ through_association.proxy_target << through_record
end
# TODO - add dependent option support
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 830aa1808a..a79bf943d1 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -18,9 +18,15 @@ module ActiveRecord
current_object = @owner.send(@reflection.through_reflection.name)
if current_object
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
- else
- @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
+ elsif new_value
+ if @owner.new_record?
+ self.target = new_value
+ through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
+ through_association.build(construct_join_attributes(new_value))
+ else
+ @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 8e7ce33814..16b6123439 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revist this to allow it for deletion, supposing dependent option is supported
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 911c908c8b..4df0f1af69 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,95 +1,25 @@
+require 'active_support/core_ext/object/tap'
+
module ActiveRecord
module AttributeMethods
- # Track unsaved attribute changes.
- #
- # A newly instantiated object is unchanged:
- # person = Person.find_by_name('Uncle Bob')
- # person.changed? # => false
- #
- # Change the name:
- # person.name = 'Bob'
- # person.changed? # => true
- # person.name_changed? # => true
- # person.name_was # => 'Uncle Bob'
- # person.name_change # => ['Uncle Bob', 'Bob']
- # person.name = 'Bill'
- # person.name_change # => ['Uncle Bob', 'Bill']
- #
- # Save the changes:
- # person.save
- # person.changed? # => false
- # person.name_changed? # => false
- #
- # Assigning the same value leaves the attribute unchanged:
- # person.name = 'Bill'
- # person.name_changed? # => false
- # person.name_change # => nil
- #
- # Which attributes have changed?
- # person.name = 'Bob'
- # person.changed # => ['name']
- # person.changes # => { 'name' => ['Bill', 'Bob'] }
- #
- # Resetting an attribute returns it to its original state:
- # person.reset_name! # => 'Bill'
- # person.changed? # => false
- # person.name_changed? # => false
- # person.name # => 'Bill'
- #
- # Before modifying an attribute in-place:
- # person.name_will_change!
- # person.name << 'y'
- # person.name_change # => ['Bill', 'Billy']
module Dirty
extend ActiveSupport::Concern
-
- DIRTY_AFFIXES = [
- { :suffix => '_changed?' },
- { :suffix => '_change' },
- { :suffix => '_will_change!' },
- { :suffix => '_was' },
- { :prefix => 'reset_', :suffix => '!' }
- ]
+ include ActiveModel::Dirty
included do
- attribute_method_affix *DIRTY_AFFIXES
-
- alias_method_chain :save, :dirty
- alias_method_chain :save!, :dirty
- alias_method_chain :update, :dirty
- alias_method_chain :reload, :dirty
+ alias_method_chain :save, :dirty
+ alias_method_chain :save!, :dirty
+ alias_method_chain :update, :dirty
+ alias_method_chain :reload, :dirty
superclass_delegating_accessor :partial_updates
self.partial_updates = true
end
- # Do any attributes have unsaved changes?
- # person.changed? # => false
- # person.name = 'bob'
- # person.changed? # => true
- def changed?
- !changed_attributes.empty?
- end
-
- # List of attributes with unsaved changes.
- # person.changed # => []
- # person.name = 'bob'
- # person.changed # => ['name']
- def changed
- changed_attributes.keys
- end
-
- # Map of changed attrs => [original value, new value].
- # person.changes # => {}
- # person.name = 'bob'
- # person.changes # => { 'name' => ['bill', 'bob'] }
- def changes
- changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
- end
-
# Attempts to +save+ the record and clears changed attributes if successful.
def save_with_dirty(*args) #:nodoc:
if status = save_without_dirty(*args)
+ @previously_changed = changes
changed_attributes.clear
end
status
@@ -97,49 +27,21 @@ module ActiveRecord
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save_with_dirty!(*args) #:nodoc:
- status = save_without_dirty!(*args)
- changed_attributes.clear
- status
+ save_without_dirty!(*args).tap do
+ @previously_changed = changes
+ changed_attributes.clear
+ end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload_with_dirty(*args) #:nodoc:
- record = reload_without_dirty(*args)
- changed_attributes.clear
- record
+ reload_without_dirty(*args).tap do
+ previously_changed_attributes.clear
+ changed_attributes.clear
+ end
end
private
- # Map of change <tt>attr => original value</tt>.
- def changed_attributes
- @changed_attributes ||= {}
- end
-
- # Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
- end
-
- # Handle <tt>*_change</tt> for +method_missing+.
- def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
- end
-
- # Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
- end
-
- # Handle <tt>reset_*!</tt> for +method_missing+.
- def reset_attribute!(attr)
- self[attr] = changed_attributes[attr] if attribute_changed?(attr)
- end
-
- # Handle <tt>*_will_change!</tt> for +method_missing+.
- def attribute_will_change!(attr)
- changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
- end
-
# Wrap write_attribute to remember original attribute value.
def write_attribute(attr, value)
attr = attr.to_s
@@ -182,23 +84,6 @@ module ActiveRecord
old != value
end
-
- module ClassMethods
- def self.extended(base)
- class << base
- alias_method_chain :alias_attribute, :dirty
- end
- end
-
- def alias_attribute_with_dirty(new_name, old_name)
- alias_attribute_without_dirty(new_name, old_name)
- DIRTY_AFFIXES.each do |affixes|
- module_eval <<-STR, __FILE__, __LINE__+1
- def #{affixes[:prefix]}#{new_name}#{affixes[:suffix]}; self.#{affixes[:prefix]}#{old_name}#{affixes[:suffix]}; end # def reset_subject!; self.reset_title!; end
- STR
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 531a698f77..c5be561ea3 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1394,7 +1394,7 @@ module ActiveRecord #:nodoc:
end
defaults << options[:default] if options[:default]
defaults.flatten!
- defaults << attribute_key_name.humanize
+ defaults << attribute_key_name.to_s.humanize
options[:count] ||= 1
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
end
@@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
conditions = attrs.map do |attr, value|
+ table_name = default_table_name
+
unless value.is_a?(Hash)
attr = attr.to_s
# Extract table name from qualified attribute names.
if attr.include?('.')
- table_name, attr = attr.split('.', 2)
- table_name = connection.quote_table_name(table_name)
+ attr_table_name, attr = attr.split('.', 2)
+ attr_table_name = connection.quote_table_name(attr_table_name)
+ else
+ attr_table_name = table_name
end
- attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
+ attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
else
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end
@@ -3013,16 +3017,22 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
- callstack.each do |name, values|
+ callstack.each do |name, values_with_empty_parameters|
begin
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ # in order to allow a date to be set without a year, we must keep the empty values.
+ # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
+ values = values_with_empty_parameters.reject(&:nil?)
+
if values.empty?
send(name + "=", nil)
else
+
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
begin
+ values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
Date.new(*values)
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
@@ -3050,10 +3060,8 @@ module ActiveRecord #:nodoc:
attribute_name = multiparameter_name.split("(").first
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
- unless value.empty?
- attributes[attribute_name] <<
- [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
- end
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
+ attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
end
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 4a88c43dff..646fed1a0b 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -197,7 +197,7 @@ module ActiveRecord
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
- elsif scope && scope[:from]
+ elsif scope && scope[:from] && !use_workaround
sql << " FROM #{scope[:from]} "
else
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index f346e3ebc8..520f3c8c0c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -315,6 +315,20 @@ module ActiveRecord
@base = base
end
+ #Handles non supported datatypes - e.g. XML
+ def method_missing(symbol, *args)
+ if symbol.to_s == 'xml'
+ xml_column_fallback(args)
+ end
+ end
+
+ def xml_column_fallback(*args)
+ case @base.adapter_name.downcase
+ when 'sqlite', 'mysql'
+ options = args.extract_options!
+ column(args[0], :text, options)
+ end
+ end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name)
@@ -705,3 +719,4 @@ module ActiveRecord
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 2473c772e3..20787ec510 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
table_definition = TableDefinition.new(self)
- table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
yield table_definition if block_given?
@@ -329,7 +329,7 @@ module ActiveRecord
schema_migrations_table.column :version, :string, :null => false
end
add_index sm_table, :version, :unique => true,
- :name => 'unique_schema_migrations'
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
# Backwards-compatibility: if we find schema_info, assume we've
# migrated up to that point:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c533d4cdb6..fab70f34b9 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -56,6 +56,13 @@ module ActiveRecord
false
end
+ # Can this adapter determine the primary key for tables not attached
+ # to an ActiveRecord class, such as join tables? Backend specific, as
+ # the abstract adapter always returns +false+.
+ def supports_primary_key?
+ false
+ end
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 2b882a1f25..4edb64c2c0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,12 +53,7 @@ module ActiveRecord
socket = config[:socket]
username = config[:username] ? config[:username].to_s : 'root'
password = config[:password].to_s
-
- if config.has_key?(:database)
- database = config[:database]
- else
- raise ArgumentError, "No database specified. Missing argument: database."
- end
+ database = config[:database]
# Require the MySQL driver and define Mysql::Result.all_hashes
unless defined? Mysql
@@ -81,7 +76,7 @@ module ActiveRecord
module ConnectionAdapters
class MysqlColumn < Column #:nodoc:
def extract_default(default)
- if type == :binary || type == :text
+ if sql_type =~ /blob/i || type == :text
if default.blank?
return null ? nil : ''
else
@@ -95,7 +90,7 @@ module ActiveRecord
end
def has_default?
- return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
super
end
@@ -213,6 +208,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def supports_savepoints? #:nodoc:
true
end
@@ -555,6 +554,12 @@ module ActiveRecord
keys.length == 1 ? [keys.first, nil] : nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
def case_sensitive_equality_operator
"= BINARY"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e77ae93c21..84cf1ad9fd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -40,6 +40,12 @@ module ActiveRecord
end
module ConnectionAdapters
+ class TableDefinition
+ def xml(*args)
+ options = args.extract_options!
+ column(args[0], 'xml', options)
+ end
+ end
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
@@ -68,7 +74,7 @@ module ActiveRecord
# depending on the server specifics
super
end
-
+
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type)
case field_type
@@ -100,10 +106,10 @@ module ActiveRecord
:string
# XML type
when /^xml$/
- :string
+ :xml
# Arrays
when /^\D+\[\]$/
- :string
+ :string
# Object identifier types
when /^oid$/
:integer
@@ -112,7 +118,7 @@ module ActiveRecord
super
end
end
-
+
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
@@ -195,7 +201,8 @@ module ActiveRecord
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "bytea" },
- :boolean => { :name => "boolean" }
+ :boolean => { :name => "boolean" },
+ :xml => { :name => "xml" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -250,6 +257,11 @@ module ActiveRecord
true
end
+ # Does PostgreSQL support finding primary key on non-ActiveRecord tables?
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
@@ -273,7 +285,7 @@ module ActiveRecord
def supports_ddl_transactions?
true
end
-
+
def supports_savepoints?
true
end
@@ -365,7 +377,7 @@ module ActiveRecord
if value.kind_of?(String) && column && column.type == :binary
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
- "xml '#{quote_string(value)}'"
+ "xml E'#{quote_string(value)}'"
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value.to_s}'"
@@ -564,7 +576,7 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
-
+
if defined?(PGconn::PQTRANS_IDLE)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
@@ -811,6 +823,12 @@ module ActiveRecord
nil
end
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
# Renames a table.
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
@@ -909,18 +927,18 @@ module ActiveRecord
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
-
+
# Returns an ORDER BY clause for the passed order option.
- #
+ #
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
-
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
-
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
end
@@ -1044,7 +1062,7 @@ module ActiveRecord
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
- # (1) $12,345,678.12
+ # (1) $12,345,678.12
# (2) $12.345.678,12
case column = row[cell_index]
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
@@ -1104,3 +1122,4 @@ module ActiveRecord
end
end
end
+
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 5eef692d05..d933bc924d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -24,11 +24,6 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
- def table_structure(table_name)
- structure = @connection.table_info(quote_table_name(table_name))
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- structure
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index c0f5046bff..5ed7094169 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -4,27 +4,6 @@ require 'active_support/core_ext/kernel/requires'
module ActiveRecord
class Base
class << self
- # Establishes a connection to the database that's used by all Active Record objects
- def sqlite_connection(config) # :nodoc:
- parse_sqlite_config!(config)
-
- unless self.class.const_defined?(:SQLite)
- require_library_or_gem(config[:adapter])
-
- db = SQLite::Database.new(config[:database], 0)
- db.show_datatypes = "ON" if !defined? SQLite::Version
- db.results_as_hash = true if defined? SQLite::Version
- db.type_translation = false
-
- # "Downgrade" deprecated sqlite API
- if SQLite.const_defined?(:Version)
- ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
- else
- ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config)
- end
- end
- end
-
private
def parse_sqlite_config!(config)
# Require database.
@@ -101,6 +80,10 @@ module ActiveRecord
true
end
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
def requires_reloading?
true
end
@@ -324,9 +307,9 @@ module ActiveRecord
end
def table_structure(table_name)
- returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- end
+ structure = @connection.table_info(quote_table_name(table_name))
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ structure
end
def alter_table(table_name, options = {}) #:nodoc:
@@ -441,18 +424,5 @@ module ActiveRecord
end
end
-
- class SQLite2Adapter < SQLiteAdapter # :nodoc:
- def rename_table(name, new_name)
- move_table(name, new_name)
- end
- end
-
- class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
- def insert(sql, name = nil, pk = nil, id_value = nil)
- execute(sql, name = nil)
- id_value || @connection.last_insert_rowid
- end
- end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6eeeddc9e1..99b812b5fc 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -622,7 +622,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
targets.each do |target|
join_fixtures["#{label}_#{target}"] = Fixture.new(
{ association.primary_key_name => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }, nil)
+ association.association_foreign_key => Fixtures.identify(target) },
+ nil, @connection)
end
end
end
@@ -706,12 +707,12 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
yaml_value.each do |fixture|
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
+ fixture.each do |name, data|
unless data
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
end
- self[name] = Fixture.new(data, model_class)
+ self[name] = Fixture.new(data, model_class, @connection)
end
end
end
@@ -724,7 +725,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
reader.each do |row|
data = {}
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
end
end
@@ -762,7 +763,8 @@ class Fixture #:nodoc:
attr_reader :model_class
- def initialize(fixture, model_class)
+ def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
+ @connection = connection
@fixture = fixture
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
end
@@ -784,14 +786,14 @@ class Fixture #:nodoc:
end
def key_list
- columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
+ columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
columns.join(", ")
end
def value_list
list = @fixture.inject([]) do |fixtures, (key, value)|
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
- fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
+ fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
end
list * ', '
end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index bf8a71d236..092f5f0023 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -23,6 +23,7 @@ en:
less_than_or_equal_to: "must be less than or equal to {{count}}"
odd: "must be odd"
even: "must be even"
+ record_invalid: "Validation failed: {{errors}}"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0baa9654b7..db5d2b25ed 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -314,7 +314,7 @@ module ActiveRecord
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
end
- unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
+ unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 94f1e8f1fd..b49471f7ab 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,60 +1,58 @@
module ActiveRecord #:nodoc:
module Serialization
- module RecordSerializer #:nodoc:
- def initialize(*args)
- super
- options[:except] |= Array.wrap(@serializable.class.inheritance_column)
+ extend ActiveSupport::Concern
+ include ActiveModel::Serializers::JSON
+
+ def serializable_hash(options = nil)
+ options ||= {}
+
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
+ options[:except] |= Array.wrap(self.class.inheritance_column)
+
+ hash = super(options)
+
+ serializable_add_includes(options) do |association, records, opts|
+ hash[association] = records.is_a?(Enumerable) ?
+ records.map { |r| r.serializable_hash(opts) } :
+ records.serializable_hash(opts)
end
+ hash
+ end
+
+ private
# Add associations specified via the <tt>:includes</tt> option.
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
- def add_includes(&block)
- if include_associations = options.delete(:include)
- base_only_or_except = { :except => options[:except],
- :only => options[:only] }
-
- include_has_options = include_associations.is_a?(Hash)
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
-
- for association in associations
- records = case @serializable.class.reflect_on_association(association).macro
- when :has_many, :has_and_belongs_to_many
- @serializable.send(association).to_a
- when :has_one, :belongs_to
- @serializable.send(association)
- end
-
- unless records.nil?
- association_options = include_has_options ? include_associations[association] : base_only_or_except
- opts = options.merge(association_options)
- yield(association, records, opts)
- end
- end
+ def serializable_add_includes(options = {})
+ return unless include_associations = options.delete(:include)
- options[:include] = include_associations
- end
- end
+ base_only_or_except = { :except => options[:except],
+ :only => options[:only] }
+
+ include_has_options = include_associations.is_a?(Hash)
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
- def serializable_hash
- hash = super
+ for association in associations
+ records = case self.class.reflect_on_association(association).macro
+ when :has_many, :has_and_belongs_to_many
+ send(association).to_a
+ when :has_one, :belongs_to
+ send(association)
+ end
- add_includes do |association, records, opts|
- hash[association] =
- if records.is_a?(Enumerable)
- records.collect { |r| self.class.new(r, opts).serializable_hash }
- else
- self.class.new(records, opts).serializable_hash
- end
+ unless records.nil?
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
+ opts = options.merge(association_options)
+ yield(association, records, opts)
+ end
end
- hash
+ options[:include] = include_associations
end
- end
end
end
require 'active_record/serializers/xml_serializer'
-require 'active_record/serializers/json_serializer'
diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb
deleted file mode 100644
index 63bf42c09d..0000000000
--- a/activerecord/lib/active_record/serializers/json_serializer.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActiveRecord #:nodoc:
- module Serialization
- extend ActiveSupport::Concern
- include ActiveModel::Serializers::JSON
-
- class JSONSerializer < ActiveModel::Serializers::JSON::Serializer
- include Serialization::RecordSerializer
- end
-
- def encode_json(encoder)
- JSONSerializer.new(self, encoder.options).to_s
- end
- end
-end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 4e172bd2b6..b19920741e 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/hash/conversions'
module ActiveRecord #:nodoc:
module Serialization
+ include ActiveModel::Serializers::Xml
+
# Builds an XML document to represent the model. Some configuration is
# available through +options+. However more complicated cases should
# override ActiveRecord::Base#to_xml.
@@ -169,18 +171,15 @@ module ActiveRecord #:nodoc:
# end
# end
def to_xml(options = {}, &block)
- serializer = XmlSerializer.new(self, options)
- block_given? ? serializer.to_s(&block) : serializer.to_s
- end
-
- def from_xml(xml)
- self.attributes = Hash.from_xml(xml).values.first
- self
+ XmlSerializer.new(self, options).serialize(&block)
end
end
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- include Serialization::RecordSerializer
+ def initialize(*args)
+ super
+ options[:except] |= Array.wrap(@serializable.class.inheritance_column)
+ end
def serializable_attributes
serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
@@ -235,7 +234,9 @@ module ActiveRecord #:nodoc:
builder.tag!(*args) do
add_attributes
procs = options.delete(:procs)
- add_includes { |association, records, opts| add_associations(association, records, opts) }
+ @serializable.send(:serializable_add_includes, options) { |association, records, opts|
+ add_associations(association, records, opts)
+ }
options[:procs] = procs
add_procs
yield builder if block_given?
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a7fa98756e..5fc41cf054 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -12,7 +12,8 @@ module ActiveRecord
attr_reader :record
def initialize(record)
@record = record
- super("Validation failed: #{@record.errors.full_messages.join(", ")}")
+ errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
+ super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index edec4e9e43..711086dc2c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -119,7 +119,7 @@ module ActiveRecord
comparison_operator = "IS ?"
elsif column.text?
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
- value = column.limit ? value.to_s[0, column.limit] : value.to_s
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
else
comparison_operator = "= ?"
end
diff --git a/activerecord/lib/active_record/validator.rb b/activerecord/lib/active_record/validator.rb
new file mode 100644
index 0000000000..83a33f4dcd
--- /dev/null
+++ b/activerecord/lib/active_record/validator.rb
@@ -0,0 +1,68 @@
+module ActiveRecord #:nodoc:
+
+ # A simple base class that can be used along with ActiveRecord::Base.validates_with
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] = "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>,
+ # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record # => The person instance being validated
+ # options # => Any non-standard options passed to validates_with
+ # end
+ # end
+ #
+ # To cause a validation error, you must add to the <tt>record<tt>'s errors directly
+ # from within the validators message
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # record.errors[:base] << "This is some custom error message"
+ # record.errors[:first_name] << "This is some complex validation"
+ # # etc...
+ # end
+ # end
+ #
+ # To add behavior to the initialize method, use the following signature:
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def initialize(record, options)
+ # super
+ # @my_custom_field = options[:field_name] || :first_name
+ # end
+ # end
+ #
+ class Validator
+ attr_reader :record, :options
+
+ def initialize(record, options)
+ @record = record
+ @options = options
+ end
+
+ def validate
+ raise "You must override this method"
+ end
+ end
+end