aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/association_preload.rb17
-rwxr-xr-xactiverecord/lib/active_record/associations.rb42
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb17
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb12
-rwxr-xr-xactiverecord/lib/active_record/associations/has_one_association.rb11
-rwxr-xr-xactiverecord/lib/active_record/base.rb11
-rwxr-xr-xactiverecord/lib/active_record/callbacks.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb13
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/mysql_adapter.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb12
-rw-r--r--activerecord/lib/active_record/dirty.rb12
-rwxr-xr-xactiverecord/lib/active_record/fixtures.rb15
-rw-r--r--activerecord/lib/active_record/named_scope.rb3
-rw-r--r--activerecord/lib/active_record/observer.rb9
-rw-r--r--activerecord/lib/active_record/test_case.rb15
-rw-r--r--activerecord/lib/active_record/transactions.rb17
-rwxr-xr-xactiverecord/lib/active_record/validations.rb66
20 files changed, 232 insertions, 118 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 49f5270396..64888f9110 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -34,7 +34,7 @@ module ActiveRecord
class_to_reflection = {}
# Not all records have the same class, so group then preload
# group on the reflection itself so that if various subclass share the same association then we do not split them
- # unncessarily
+ # unnecessarily
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
send("preload_#{reflection.macro}_association", records, reflection, preload_options)
@@ -188,7 +188,6 @@ module ActiveRecord
through_records
end
- # FIXME: quoting
def preload_belongs_to_association(records, reflection, preload_options={})
options = reflection.options
primary_key_name = reflection.primary_key_name
@@ -227,9 +226,19 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
- conditions = "#{table_name}.#{primary_key} IN (?)"
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
conditions << append_conditions(options, preload_options)
- associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
+ ids = id_map.keys.uniq.map do |id|
+ if column_type == :integer
+ id.to_i
+ elsif column_type == :float
+ id.to_f
+ else
+ id
+ end
+ end
+ associated_records = klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
:joins => options[:joins],
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4b7154043f..fd9a443eb9 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -582,12 +582,13 @@ module ActiveRecord
# has_many :clients
# end
#
- # class Company < ActiveRecord::Base; end
+ # class Client < ActiveRecord::Base; end
# end
# end
#
- # When Firm#clients is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
- # with a class in another module scope, this can be done by specifying the complete class name. Example:
+ # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
+ # If you want to associate with a class in another module scope, this can be done by specifying the complete class name.
+ # Example:
#
# module MyApplication
# module Business
@@ -663,6 +664,7 @@ module ActiveRecord
# * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
# as the default <tt>:foreign_key</tt>.
+ # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
# * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
@@ -678,7 +680,7 @@ module ActiveRecord
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:through</tt> - 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>
@@ -691,6 +693,7 @@ module ActiveRecord
# * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -758,6 +761,7 @@ module ActiveRecord
# * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
# as the default <tt>:foreign_key</tt>.
+ # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
@@ -772,6 +776,7 @@ module ActiveRecord
# association is a polymorphic +belongs_to+.
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -861,6 +866,7 @@ module ActiveRecord
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -1032,6 +1038,7 @@ module ActiveRecord
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -1107,6 +1114,8 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
+ new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
+
if association_proxy_class == HasOneThroughAssociation
association.create_through_record(new_value)
self.send(reflection.name, new_value)
@@ -1143,7 +1152,7 @@ module ActiveRecord
end
define_method("#{reflection.name.to_s.singularize}_ids") do
- send(reflection.name).map(&:id)
+ send(reflection.name).map { |record| record.id }
end
end
@@ -1347,7 +1356,7 @@ module ActiveRecord
def create_has_many_reflection(association_id, options, &extension)
options.assert_valid_keys(
- :class_name, :table_name, :foreign_key,
+ :class_name, :table_name, :foreign_key, :primary_key,
:dependent,
:select, :conditions, :include, :order, :group, :limit, :offset,
:as, :through, :source, :source_type,
@@ -1355,7 +1364,7 @@ module ActiveRecord
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :accessible
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1365,7 +1374,7 @@ module ActiveRecord
def create_has_one_reflection(association_id, options)
options.assert_valid_keys(
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible
)
create_reflection(:has_one, association_id, options, self)
@@ -1381,7 +1390,7 @@ module ActiveRecord
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
- :counter_cache, :extend, :polymorphic, :readonly, :validate
+ :counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible
)
reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1401,7 +1410,7 @@ module ActiveRecord
:finder_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :accessible
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1478,25 +1487,30 @@ module ActiveRecord
join_dependency.joins_for_table_name(table)
}.flatten.compact.uniq
+ order = options[:order]
+ if scoped_order = (scope && scope[:order])
+ order = order ? "#{order}, #{scoped_order}" : scoped_order
+ end
+
is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
sql = "SELECT "
if is_distinct
- sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
else
sql << primary_key
end
sql << " FROM #{connection.quote_table_name table_name} "
if is_distinct
- sql << distinct_join_associations.collect(&:association_join).join
+ sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
add_joins!(sql, options, scope)
end
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
- if options[:order] && is_distinct
- connection.add_order_by_for_association_limiting!(sql, options)
+ if order && is_distinct
+ connection.add_order_by_for_association_limiting!(sql, :order => order)
else
add_order!(sql, options[:order], scope)
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index bbd8af7e76..a28be9eed1 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# If using a custom finder_sql, scan the entire collection.
if @reflection.options[:finder_sql]
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.uniq.map(&:to_i)
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
if ids.size == 1
id = ids.first
@@ -78,11 +78,14 @@ module ActiveRecord
@loaded = false
end
- def build(attributes = {})
+ def build(attributes = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| build(attr) }
+ attributes.collect { |attr| build(attr, &block) }
else
- build_record(attributes) { |record| set_belongs_to_association_for(record) }
+ build_record(attributes) do |record|
+ block.call(record) if block_given?
+ set_belongs_to_association_for(record)
+ end
end
end
@@ -94,6 +97,8 @@ module ActiveRecord
@owner.transaction do
flatten_deeper(records).each do |record|
+ record = @reflection.klass.new(record) if @reflection.options[:accessible] && record.is_a?(Hash)
+
raise_on_type_mismatch(record)
add_record_to_target_with_callbacks(record) do |r|
result &&= insert_record(record) unless @owner.new_record?
@@ -226,6 +231,10 @@ module ActiveRecord
# Replace this collection with +other_array+
# This will perform a diff and delete/add only records that have changed.
def replace(other_array)
+ other_array.map! do |val|
+ val.is_a?(Hash) ? @reflection.klass.new(val) : val
+ end if @reflection.options[:accessible]
+
other_array.each { |val| raise_on_type_mismatch(val) }
load_target
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 37440aa84d..e6fa15c173 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -19,6 +19,14 @@ module ActiveRecord
end
protected
+ def owner_quoted_id
+ if @reflection.options[:primary_key]
+ quote_value(@owner.send(@reflection.options[:primary_key]))
+ else
+ @owner.quoted_id
+ end
+ end
+
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name)
@@ -53,9 +61,9 @@ module ActiveRecord
def delete_records(records)
case @reflection.options[:dependent]
when :destroy
- records.each(&:destroy)
+ records.each { |r| r.destroy }
when :delete_all
- @reflection.klass.delete(records.map(&:id))
+ @reflection.klass.delete(records.map { |record| record.id })
else
ids = quoted_record_ids(records)
@reflection.klass.update_all(
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 25a268e95c..fdc0fa52c9 100755
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -47,7 +47,16 @@ module ActiveRecord
return (obj.nil? ? nil : self)
end
end
-
+
+ protected
+ def owner_quoted_id
+ if @reflection.options[:primary_key]
+ quote_value(@owner.send(@reflection.options[:primary_key]))
+ else
+ @owner.quoted_id
+ end
+ end
+
private
def find_target
@reflection.klass.find(:first,
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 962c2b36d9..a75e1a5b24 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -6,7 +6,7 @@ module ActiveRecord #:nodoc:
class ActiveRecordError < StandardError
end
- # Raised when the single-table inheritance mechanism failes to locate the subclass
+ # Raised when the single-table inheritance mechanism fails to locate the subclass
# (for example due to improper usage of column that +inheritance_column+ points to).
class SubclassNotFound < ActiveRecordError #:nodoc:
end
@@ -97,7 +97,7 @@ module ActiveRecord #:nodoc:
class MissingAttributeError < NoMethodError
end
- # Raised when an error occured while doing a mass assignment to an attribute through the
+ # Raised when an error occurred while doing a mass assignment to an attribute through the
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
@@ -271,7 +271,7 @@ module ActiveRecord #:nodoc:
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
#
# # No 'Winter' tag exists
# winter = Tag.find_or_initialize_by_name("Winter")
@@ -724,8 +724,7 @@ module ActiveRecord #:nodoc:
# ==== Attributes
#
# * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
- # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
- # See conditions in the intro for more info.
+ # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
# * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
@@ -828,7 +827,7 @@ module ActiveRecord #:nodoc:
def update_counters(id, counters)
updates = counters.inject([]) { |list, (counter_name, increment)|
sign = increment < 0 ? "-" : "+"
- list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
}.join(", ")
update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 4edc209c65..be2621fdb6 100755
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -50,7 +50,7 @@ module ActiveRecord
#
# == Inheritable callback queues
#
- # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
# Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
# hierarchy. Example:
#
@@ -262,7 +262,7 @@ module ActiveRecord
def valid_with_callbacks? #:nodoc:
return false if callback(:before_validation) == false
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
- return false if result == false
+ return false if false == result
result = valid_without_callbacks?
@@ -293,7 +293,7 @@ module ActiveRecord
private
def callback(method)
- result = run_callbacks(method) { |result, object| result == false }
+ result = run_callbacks(method) { |result, object| false == result }
if result != false && respond_to_without_attributes?(method)
result = send(method)
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 2c03de0f17..31d6c7942c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -1,4 +1,5 @@
require 'date'
+require 'set'
require 'bigdecimal'
require 'bigdecimal/util'
@@ -6,6 +7,8 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
# An abstract definition of a column in a table.
class Column
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
+
module Format
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
@@ -135,11 +138,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value == true || value == false
- value
- else
- %w(true t 1).include?(value.to_s.downcase)
- end
+ TRUE_VALUES.include?(value)
end
# convert something to a BigDecimal
@@ -257,7 +256,10 @@ module ActiveRecord
def to_sql
column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
+ column_options = {}
+ column_options[:null] = null unless null.nil?
+ column_options[:default] = default unless default.nil?
+ add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
column_sql
end
alias to_s :to_sql
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 7d8530ebef..0f60a91ef1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -383,7 +383,7 @@ module ActiveRecord
def add_column_options!(sql, options) #:nodoc:
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
- # must explcitly check for :null to allow change_column to work on migrations
+ # must explicitly check for :null to allow change_column to work on migrations
if options.has_key? :null
if options[:null] == false
sql << " NOT NULL"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f48b107a2a..47dbf5a5f3 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -118,6 +118,19 @@ module ActiveRecord
@connection
end
+ def open_transactions
+ @open_transactions ||= 0
+ end
+
+ def increment_open_transactions
+ @open_transactions ||= 0
+ @open_transactions += 1
+ end
+
+ def decrement_open_transactions
+ @open_transactions -= 1
+ end
+
def log_info(sql, name, runtime)
if @logger && @logger.debug?
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c5962764f5..35b9ed4746 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -69,7 +69,7 @@ module ActiveRecord
MysqlCompat.define_all_hashes_method!
mysql = Mysql.init
- mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
end
@@ -145,6 +145,7 @@ module ActiveRecord
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
@@ -436,18 +437,29 @@ module ActiveRecord
end
def change_column_default(table_name, column_name, default) #:nodoc:
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ column = column_for(table_name, column_name)
+
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
- execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
+ change_column table_name, column_name, column.sql_type, :null => null
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ column = column_for(table_name, column_name)
+
unless options_include_default?(options)
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- else
- raise "No such column: #{table_name}.#{column_name}"
- end
+ options[:default] = column.default
+ end
+
+ unless options.has_key?(:null)
+ options[:null] = column.null
end
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
@@ -459,6 +471,7 @@ module ActiveRecord
options = {}
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
+ options[:null] = column.null
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
@@ -507,7 +520,9 @@ module ActiveRecord
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
end
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
+ if @config[:sslca] || @config[:sslkey]
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
+ end
@connection.real_connect(*@connection_options)
@@ -533,6 +548,13 @@ module ActiveRecord
def version
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2e2d50ccf4..6a20f41a4b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -49,7 +49,6 @@ module ActiveRecord
private
def extract_limit(sql_type)
case sql_type
- when /^integer/i; 4
when /^bigint/i; 8
when /^smallint/i; 2
else super
@@ -623,6 +622,19 @@ module ActiveRecord
end
end
+ # Returns the current database name.
+ def current_database
+ query('select current_database()')[0][0]
+ end
+
+ # Returns the current database encoding format.
+ def encoding
+ query(<<-end_sql)[0][0]
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
+ WHERE pg_database.datname LIKE '#{current_database}'
+ end_sql
+ end
+
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
@@ -855,7 +867,7 @@ module ActiveRecord
end
private
- # The internal PostgreSQL identifer of the money data type.
+ # The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# Connects to a PostgreSQL server and sets up the adapter depending on the
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 51cfd10e5c..84f8c0284e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -238,6 +238,15 @@ module ActiveRecord
end
end
+ def change_column_null(table_name, column_name, null, default = nil)
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+ alter_table(table_name) do |definition|
+ definition[column_name].null = null
+ end
+ end
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
alter_table(table_name) do |definition|
include_default = options_include_default?(options)
@@ -251,6 +260,9 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
+ end
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index a7d767486c..4ce0356457 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -62,7 +62,7 @@ module ActiveRecord
changed_attributes.keys
end
- # Map of changed attrs => [original value, new value]
+ # Map of changed attrs => [original value, new value].
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { 'name' => ['bill', 'bob'] }
@@ -93,27 +93,27 @@ module ActiveRecord
end
private
- # Map of change attr => original value.
+ # Map of change <tt>attr => original value</tt>.
def changed_attributes
@changed_attributes ||= {}
end
- # Handle *_changed? for method_missing.
+ # Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr)
changed_attributes.include?(attr)
end
- # Handle *_change for method_missing.
+ # Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle *_was for method_missing.
+ # Handle <tt>*_was</tt> for +method_missing+.
def attribute_was(attr)
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
- # Handle *_will_change! for method_missing.
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e19614e31f..622cfc3c3f 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -515,7 +515,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
all_loaded_fixtures.update(fixtures_map)
- connection.transaction(Thread.current['open_transactions'].to_i == 0) do
+ connection.transaction(connection.open_transactions.zero?) do
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
fixtures.each { |fixture| fixture.insert_fixtures }
@@ -541,10 +541,11 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
label.to_s.hash.abs
end
- attr_reader :table_name
+ attr_reader :table_name, :name
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
+ @name = table_name # preserve fixture base name
@class_name = class_name ||
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
@table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
@@ -929,7 +930,7 @@ module Test #:nodoc:
load_fixtures
@@already_loaded_fixtures[self.class] = @loaded_fixtures
end
- ActiveRecord::Base.send :increment_open_transactions
+ ActiveRecord::Base.connection.increment_open_transactions
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
@@ -950,9 +951,9 @@ module Test #:nodoc:
end
# Rollback changes if a transaction is active.
- if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
+ if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
ActiveRecord::Base.connection.rollback_db_transaction
- Thread.current['open_transactions'] = 0
+ ActiveRecord::Base.connection.decrement_open_transactions
end
ActiveRecord::Base.verify_active_connections!
end
@@ -963,9 +964,9 @@ module Test #:nodoc:
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
unless fixtures.nil?
if fixtures.instance_of?(Fixtures)
- @loaded_fixtures[fixtures.table_name] = fixtures
+ @loaded_fixtures[fixtures.name] = fixtures
else
- fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
end
end
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index eac61e9e43..080e3d0f5e 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -150,7 +150,8 @@ module ActiveRecord
if scopes.include?(method)
scopes[method].call(self, *args)
else
- with_scope :find => proxy_options do
+ with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do
+ method = :new if method == :build
proxy_scope.send(method, *args, &block)
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 25e0e61c69..c96e5f9d51 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -20,7 +20,7 @@ module ActiveRecord
# ActiveRecord::Base.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
- # called during startup, and before each development request.
+ # called during startup, and before each development request.
def observers=(*observers)
@observers = observers.flatten
end
@@ -130,11 +130,11 @@ module ActiveRecord
# Observers register themselves in the model class they observe, since it is the class that
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
# corresponding model class is loaded.
- #
+ #
# Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
- # application initializers. Now observers are loaded after application initializers,
+ # application initializers. Now observers are loaded after application initializers,
# so observed models can make use of extensions.
- #
+ #
# If by any chance you are using observed models in the initialization you can still
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
# singletons and that call instantiates and registers them.
@@ -189,7 +189,6 @@ module ActiveRecord
def add_observer!(klass)
klass.add_observer(self)
- klass.class_eval 'def after_find() end' unless klass.method_defined?(:after_find)
end
end
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 7dee962c8a..ca5591ae35 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -22,11 +22,22 @@ module ActiveRecord
end
end
+ def assert_sql(*patterns_to_match)
+ $queries_executed = []
+ yield
+ ensure
+ failed_patterns = []
+ patterns_to_match.each do |pattern|
+ failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
+ end
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found."
+ end
+
def assert_queries(num = 1)
- $query_count = 0
+ $queries_executed = []
yield
ensure
- assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
+ assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3b6835762c..354a6c83a2 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -73,25 +73,14 @@ module ActiveRecord
# trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
module ClassMethods
def transaction(&block)
- increment_open_transactions
+ connection.increment_open_transactions
begin
- connection.transaction(Thread.current['start_db_transaction'], &block)
+ connection.transaction(connection.open_transactions == 1, &block)
ensure
- decrement_open_transactions
+ connection.decrement_open_transactions
end
end
-
- private
- def increment_open_transactions #:nodoc:
- open = Thread.current['open_transactions'] ||= 0
- Thread.current['start_db_transaction'] = open.zero?
- Thread.current['open_transactions'] = open + 1
- end
-
- def decrement_open_transactions #:nodoc:
- Thread.current['open_transactions'] -= 1
- end
end
def transaction(&block)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index c4e370d017..2647fbba92 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -1,5 +1,5 @@
module ActiveRecord
- # Raised by save! and create! when the record is invalid. Use the
+ # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
# +record+ method to retrieve the record which did not validate.
# begin
# complex_operation_that_calls_save!_internally
@@ -52,7 +52,7 @@ module ActiveRecord
# Adds an error to the base object instead of any particular attribute. This is used
# to report errors that don't tie to any specific attribute, but rather to the object
# as a whole. These error messages don't get prepended with any field name when iterating
- # with each_full, so they should be complete sentences.
+ # with +each_full+, so they should be complete sentences.
def add_to_base(msg)
add(:base, msg)
end
@@ -97,7 +97,7 @@ module ActiveRecord
!@errors[attribute.to_s].nil?
end
- # Returns nil, if no errors are associated with the specified +attribute+.
+ # Returns +nil+, if no errors are associated with the specified +attribute+.
# Returns the error message, if one error is associated with the specified +attribute+.
# Returns an array of error messages, if more than one error is associated with the specified +attribute+.
#
@@ -118,7 +118,7 @@ module ActiveRecord
alias :[] :on
- # Returns errors assigned to the base object through add_to_base according to the normal rules of on(attribute).
+ # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
def on_base
on(:base)
end
@@ -131,15 +131,15 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" } # =>
- # name - is too short (minimum is 5 characters)
- # name - can't be blank
- # address - can't be blank
+ # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
+ # # => name - is too short (minimum is 5 characters)
+ # # name - can't be blank
+ # # address - can't be blank
def each
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
end
- # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
+ # Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
# through iteration as "First name can't be empty".
#
# class Company < ActiveRecord::Base
@@ -148,10 +148,10 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.each_full{|msg| puts msg } # =>
- # Name is too short (minimum is 5 characters)
- # Name can't be blank
- # Address can't be blank
+ # company.errors.each_full{|msg| puts msg }
+ # # => Name is too short (minimum is 5 characters)
+ # # Name can't be blank
+ # # Address can't be blank
def each_full
full_messages.each { |msg| yield msg }
end
@@ -164,8 +164,8 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ # company.errors.full_messages
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages
full_messages = []
@@ -209,13 +209,13 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.to_xml # =>
- # <?xml version="1.0" encoding="UTF-8"?>
- # <errors>
- # <error>Name is too short (minimum is 5 characters)</error>
- # <error>Name can't be blank</error>
- # <error>Address can't be blank</error>
- # </errors>
+ # company.errors.to_xml
+ # # => <?xml version="1.0" encoding="UTF-8"?>
+ # # <errors>
+ # # <error>Name is too short (minimum is 5 characters)</error>
+ # # <error>Name can't be blank</error>
+ # # <error>Address can't be blank</error>
+ # # </errors>
def to_xml(options={})
options[:root] ||= "errors"
options[:indent] ||= 2
@@ -261,7 +261,7 @@ module ActiveRecord
# person.errors.on "phone_number" # => "has invalid format"
# person.errors.each_full { |msg| puts msg }
# # => "Last name can't be empty\n" +
- # "Phone number has invalid format"
+ # # "Phone number has invalid format"
#
# person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
# person.save # => true (and person is now saved in the database)
@@ -300,7 +300,7 @@ module ActiveRecord
:odd => 'odd?', :even => 'even?' }.freeze
# Adds a validation method or block to the class. This is useful when
- # overriding the +validate+ instance method becomes too unwieldly and
+ # overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
#
# This can be done with a symbol pointing to a method:
@@ -479,8 +479,9 @@ module ActiveRecord
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
- # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
+ # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
+ # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
@@ -491,7 +492,6 @@ module ActiveRecord
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- #
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
@@ -503,12 +503,16 @@ module ActiveRecord
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
+ # count words as in above example.)
+ # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attrs)
# Merge given options with defaults.
options = {
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
- :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
+ :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length],
+ :tokenizer => lambda {|value| value.split(//)}
}.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
@@ -535,7 +539,7 @@ module ActiveRecord
too_long = options[:too_long] % option_value.end
validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
record.errors.add(attr, too_short)
elsif value.size > option_value.end
@@ -552,7 +556,7 @@ module ActiveRecord
message = (options[:message] || options[message_options[option]]) % option_value
validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
end
end
@@ -850,7 +854,7 @@ module ActiveRecord
raw_value = raw_value.to_i
else
begin
- raw_value = Kernel.Float(raw_value.to_s)
+ raw_value = Kernel.Float(raw_value)
rescue ArgumentError, TypeError
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
next