aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb22
-rw-r--r--activerecord/lib/active_record/association_preload.rb8
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/associations.rb99
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb29
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb18
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb24
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb6
-rwxr-xr-xactiverecord/lib/active_record/base.rb257
-rw-r--r--activerecord/lib/active_record/calculations.rb19
-rw-r--r--activerecord/lib/active_record/callbacks.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb281
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb242
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb8
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/connection_adapters/abstract_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb19
-rw-r--r--activerecord/lib/active_record/dirty.rb5
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb40
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/locale/en-US.rb25
-rw-r--r--activerecord/lib/active_record/locale/en-US.yml33
-rw-r--r--activerecord/lib/active_record/migration.rb47
-rw-r--r--activerecord/lib/active_record/named_scope.rb12
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/test_case.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb16
-rw-r--r--activerecord/lib/active_record/validations.rb140
33 files changed, 899 insertions, 606 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 17a7949959..a6bbd6fc82 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -21,17 +21,12 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-$:.unshift(File.dirname(__FILE__)) unless
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
-
-unless defined? ActiveSupport
- active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
- if File.exist?(active_support_path)
- $:.unshift active_support_path
- require 'active_support'
- else
- require 'rubygems'
- gem 'activesupport'
+begin
+ require 'active_support'
+rescue LoadError
+ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
+ if File.directory?(activesupport_path)
+ $:.unshift activesupport_path
require 'active_support'
end
end
@@ -56,6 +51,7 @@ require 'active_record/calculations'
require 'active_record/serialization'
require 'active_record/attribute_methods'
require 'active_record/dirty'
+require 'active_record/dynamic_finder_match'
ActiveRecord::Base.class_eval do
extend ActiveRecord::QueryCache
@@ -81,7 +77,5 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
-I18n.backend.populate do
- require 'active_record/locale/en-US.rb'
-end
+I18n.load_translations File.dirname(__FILE__) + '/active_record/locale/en-US.yml'
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index c7594809b7..61fa34ac39 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -51,9 +51,7 @@ module ActiveRecord
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
parent_records.each do |parent_record|
- association_proxy = parent_record.send(reflection_name)
- association_proxy.loaded
- association_proxy.target = associated_record
+ parent_record.send("set_#{reflection_name}_target", associated_record)
end
end
@@ -112,8 +110,8 @@ module ActiveRecord
def preload_has_one_association(records, reflection, preload_options={})
id_to_record_map, ids = construct_id_map(records)
options = reflection.options
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
if options[:through]
- records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
through_records = preload_through_records(records, reflection, options[:through])
through_reflection = reflections[options[:through]]
through_primary_key = through_reflection.primary_key_name
@@ -126,8 +124,6 @@ module ActiveRecord
end
end
else
- records.each {|record| record.send("set_#{reflection.name}_target", nil)}
-
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index eb1281901b..6405071354 100644..100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -878,10 +878,10 @@ module ActiveRecord
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
- if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
- association["#{reflection.primary_key_name}"] = id
+ if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
+ association[reflection.primary_key_name] = id
association.save(true)
end
end
@@ -994,7 +994,7 @@ module ActiveRecord
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
if association && association.target
if association.new_record?
@@ -1002,8 +1002,8 @@ module ActiveRecord
end
if association.updated?
- self["#{reflection.primary_key_name}"] = association.id
- self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
+ self[reflection.primary_key_name] = association.id
+ self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
end
end
@@ -1015,7 +1015,7 @@ module ActiveRecord
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
if !association.nil?
if association.new_record?
@@ -1023,7 +1023,7 @@ module ActiveRecord
end
if association.updated?
- self["#{reflection.primary_key_name}"] = association.id
+ self[reflection.primary_key_name] = association.id
end
end
end
@@ -1038,15 +1038,15 @@ module ActiveRecord
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ association = send(reflection.name)
+ association.class.increment_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
end
after_create method_name
method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ association = send(reflection.name)
+ association.class.decrement_counter(cache_column, send(reflection.primary_key_name)) unless association.nil?
end
before_destroy method_name
@@ -1164,6 +1164,9 @@ module ActiveRecord
# If true, duplicate associated objects will be ignored by accessors and query methods.
# [:finder_sql]
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
+ # [:counter_sql]
+ # Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:delete_sql]
# Overwrite the default generated SQL statement used to remove links between the associated
# classes with a manual statement.
@@ -1269,10 +1272,9 @@ module ActiveRecord
association.create_through_record(new_value)
self.send(reflection.name, new_value)
else
- association.replace(new_value)
+ association.replace(new_value)
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
end
-
- instance_variable_set(ivar, new_value.nil? ? nil : association)
end
define_method("set_#{reflection.name}_target") do |target|
@@ -1301,7 +1303,11 @@ module ActiveRecord
end
define_method("#{reflection.name.to_s.singularize}_ids") do
- send(reflection.name).map { |record| record.id }
+ if send(reflection.name).loaded?
+ send(reflection.name).map(&:id)
+ else
+ send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
+ end
end
end
@@ -1322,19 +1328,19 @@ module ActiveRecord
end
end
end
-
+
def add_single_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = instance_variable_get("@#{association_name}")
if !association.nil?
- errors.add "#{association_name}" unless association.target.nil? || association.valid?
+ errors.add association_name unless association.target.nil? || association.valid?
end
end
-
+
validate method_name
end
-
+
def add_multiple_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
ivar = "@#{association_name}"
@@ -1350,7 +1356,7 @@ module ActiveRecord
else
association.target.select { |record| record.new_record? }
end.each do |record|
- errors.add "#{association_name}" unless record.valid?
+ errors.add association_name unless record.valid?
end
end
end
@@ -1370,7 +1376,7 @@ module ActiveRecord
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
records_to_save = if @new_record_before_save
association
@@ -1437,7 +1443,7 @@ module ActiveRecord
when :destroy
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- send("#{reflection.name}").each { |o| o.destroy }
+ send(reflection.name).each { |o| o.destroy }
end
before_destroy method_name
when :delete_all
@@ -1456,22 +1462,22 @@ module ActiveRecord
when :destroy
method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.destroy unless association.nil?
end
before_destroy method_name
when :delete
method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.class.delete(association.id) unless association.nil?
end
before_destroy method_name
when :nullify
method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
- association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil?
+ association = send(reflection.name)
+ association.update_attribute(reflection.primary_key_name, nil) unless association.nil?
end
before_destroy method_name
else
@@ -1486,14 +1492,14 @@ module ActiveRecord
when :destroy
method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.destroy unless association.nil?
end
before_destroy method_name
when :delete
method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
define_method(method_name) do
- association = send("#{reflection.name}")
+ association = send(reflection.name)
association.class.delete(association.id) unless association.nil?
end
before_destroy method_name
@@ -1528,7 +1534,7 @@ module ActiveRecord
create_reflection(:has_one, association_id, options, self)
end
-
+
def create_has_one_through_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
@@ -1596,7 +1602,7 @@ module ActiveRecord
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -1652,7 +1658,7 @@ module ActiveRecord
if is_distinct
sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
end
add_conditions!(sql, options[:conditions], scope)
@@ -1678,19 +1684,19 @@ module ActiveRecord
else all << cond
end
end
- conditions.join(' ').scan(/([\.\w]+).?\./).flatten
+ conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
end
def order_tables(options)
order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
- order.scan(/([\.\w]+).?\./).flatten
+ order.scan(/([\.a-zA-Z_]+).?\./).flatten
end
def selects_tables(options)
select = options[:select]
return [] unless select && select.is_a?(String)
- select.scan(/"?([\.\w]+)"?.?\./).flatten
+ select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
end
# Checks if the conditions reference a table other than the current model table
@@ -1892,6 +1898,7 @@ module ActiveRecord
collection.target.push(association)
when :has_one
return if record.id.to_s != join.parent.record_id(row).to_s
+ return if record.instance_variable_defined?("@#{join.reflection.name}")
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
record.send("set_#{join.reflection.name}_target", association)
when :belongs_to
@@ -1919,7 +1926,7 @@ module ActiveRecord
end
def aliased_primary_key
- "#{ aliased_prefix }_r0"
+ "#{aliased_prefix}_r0"
end
def aliased_table_name
@@ -1931,7 +1938,7 @@ module ActiveRecord
@column_names_with_alias = []
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
- @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
end
end
@@ -1968,12 +1975,12 @@ module ActiveRecord
@aliased_prefix = "t#{ join_dependency.joins.size }"
@parent_table_name = parent.active_record.table_name
@aliased_table_name = aliased_table_name_for(table_name)
-
+
if reflection.macro == :has_and_belongs_to_many
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
end
-
- if reflection.macro == :has_many && reflection.options[:through]
+
+ if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
@aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
end
end
@@ -1997,7 +2004,7 @@ module ActiveRecord
]
when :has_many, :has_one
case
- when reflection.macro == :has_many && reflection.options[:through]
+ when reflection.options[:through]
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
@@ -2098,10 +2105,8 @@ module ActiveRecord
else
""
end || ''
- join << %(AND %s.%s = %s ) % [
- connection.quote_table_name(aliased_table_name),
- connection.quote_column_name(klass.inheritance_column),
- klass.quote_value(klass.sti_name)] unless klass.descends_from_active_record?
+ join << %(AND %s) % [
+ klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
@@ -2111,7 +2116,7 @@ module ActiveRecord
end
protected
-
+
def aliased_table_name_for(name, suffix = nil)
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
@join_dependency.table_aliases[name] += 1
@@ -2129,7 +2134,7 @@ module ActiveRecord
name
end
-
+
def pluralize(table_name)
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index a12d4face4..168443e092 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -141,6 +141,35 @@ module ActiveRecord
end
end
+ # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
+ # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
+ # descendant's +construct_sql+ method will have set :counter_sql automatically.
+ # Otherwise, construct options and pass them with scope to the target class's +count+.
+ def count(*args)
+ if @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
+ else
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
+ if @reflection.options[:uniq]
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
+ options.merge!(:distinct => true)
+ end
+
+ value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
+
+ limit = @reflection.options[:limit]
+ offset = @reflection.options[:offset]
+
+ if limit || offset
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
+ else
+ value
+ end
+ end
+ end
+
+
# Remove +records+ from this association. Does not destroy +records+.
def delete(*records)
records = flatten_deeper(records)
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 78b4c137a7..acdcd14ec8 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -74,8 +74,8 @@ module ActiveRecord
end
# Does the proxy or its \target respond to +symbol+?
- def respond_to?(symbol, include_priv = false)
- proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
+ def respond_to?(*args)
+ proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
end
# Forwards <tt>===</tt> explicitly to the \target because the instance method
@@ -155,18 +155,6 @@ module ActiveRecord
records.map { |record| record.quoted_id }.join(',')
end
- # Interpolates the SQL in <tt>options[key]</tt> and assigns the result
- # back, for any +key+ in +keys+ that's present in +options+.
- #
- # Meant to be used like this:
- #
- # interpolate_sql_options!(@reflection.options, :finder_sql)
- #
- def interpolate_sql_options!(options, *keys)
- keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
- end
-
- # Forwards the call to the owner.
def interpolate_sql(sql, record = nil)
@owner.send(:interpolate_sql, sql, record)
end
@@ -261,7 +249,7 @@ module ActiveRecord
# Array#flatten has problems with recursive arrays. Going one level
# deeper solves the majority of the problems.
def flatten_deeper(array)
- array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
+ array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
end
# Returns the ID of the owner, quoted if needed.
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 d516d54151..3d689098b5 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
@@ -70,16 +70,24 @@ module ActiveRecord
end
def construct_sql
- interpolate_sql_options!(@reflection.options, :finder_sql)
-
if @reflection.options[:finder_sql]
- @finder_sql = @reflection.options[:finder_sql]
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
else
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
@finder_sql << " AND (#{conditions})" if conditions
end
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
+
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ else
+ @counter_sql = @finder_sql
+ end
end
def construct_scope
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f06e69aba3..dda22668c6 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -5,23 +5,6 @@ module ActiveRecord
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < AssociationCollection #:nodoc:
- # Count the number of associated records. All arguments are optional.
- def count(*args)
- if @reflection.options[:counter_sql]
- @reflection.klass.count_by_sql(@counter_sql)
- elsif @reflection.options[:finder_sql]
- @reflection.klass.count_by_sql(@finder_sql)
- else
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- options[:conditions] = options[:conditions].blank? ?
- @finder_sql :
- @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
- options[:include] ||= @reflection.options[:include]
-
- @reflection.klass.count(column_name, options)
- end
- end
-
protected
def owner_quoted_id
if @reflection.options[:primary_key]
@@ -49,8 +32,11 @@ module ActiveRecord
else
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
end
-
- @target = [] and loaded if count == 0
+
+ # If there's nothing in the database and @target has no new records
+ # we are certain the current target is an empty array. This is a
+ # documented side-effect of the method that may avoid an extra SELECT.
+ @target ||= [] and loaded if count == 0
if @reflection.options[:limit]
count = [ @reflection.options[:limit], count ].min
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 e1bfff5923..84fa900f46 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -31,16 +31,6 @@ module ActiveRecord
return count
end
- def count(*args)
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- if @reflection.options[:uniq]
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
- column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
- options.merge!(:distinct => true)
- end
- @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
- end
-
protected
def construct_find_options!(options)
options[:select] = construct_select(options[:select])
@@ -237,7 +227,7 @@ module ActiveRecord
end
def build_sti_condition
- "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
+ @reflection.through_reflection.klass.send(:type_condition)
end
alias_method :sql_conditions, :conditions
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index fdc0fa52c9..18733255d2 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -21,8 +21,8 @@ module ActiveRecord
def replace(obj, dont_save = false)
load_target
- unless @target.nil?
- if dependent? && !dont_save && @target != obj
+ unless @target.nil? || @target == obj
+ if dependent? && !dont_save
@target.destroy unless @target.new_record?
@owner.clear_association_cache
else
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 c846956e1f..b78bd5d931 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -22,6 +22,10 @@ module ActiveRecord
def find_target
super.first
+ end
+
+ def reset_target!
+ @target = nil
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index fab16a4446..0a1baff87d 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -214,7 +214,7 @@ module ActiveRecord
if logger
logger.warn "Exception occurred during reader method compilation."
logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
- logger.warn "#{err.message}"
+ logger.warn err.message
end
end
end
@@ -330,8 +330,8 @@ module ActiveRecord
end
end
- # A Person object with a name attribute can ask <tt>person.respond_to?("name")</tt>,
- # <tt>person.respond_to?("name=")</tt>, and <tt>person.respond_to?("name?")</tt>
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
# which will all return +true+.
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_priv = false)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index b282ea931e..3419aad580 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -452,13 +452,6 @@ module ActiveRecord #:nodoc:
cattr_accessor :default_timezone, :instance_writer => false
@@default_timezone = :local
- # Determines whether to use a connection for each thread, or a single shared connection for all threads.
- # Defaults to false. If you're writing a threaded application, set to true
- # and periodically call verify_active_connections! to clear out connections
- # assigned to stale threads.
- cattr_accessor :allow_concurrency, :instance_writer => false
- @@allow_concurrency = false
-
# Specifies the format to use when dumping the database schema with Rails'
# Rakefile. If :sql, the schema is dumped as (potentially database-
# specific) SQL statements. If :ruby, the schema is dumped as an
@@ -930,12 +923,12 @@ module ActiveRecord #:nodoc:
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
def attr_protected(*attributes)
- write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
end
# Returns an array of all the attributes that have been protected from mass-assignment.
def protected_attributes # :nodoc:
- read_inheritable_attribute("attr_protected")
+ read_inheritable_attribute(:attr_protected)
end
# Specifies a white list of model attributes that can be set via
@@ -963,22 +956,22 @@ module ActiveRecord #:nodoc:
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
def attr_accessible(*attributes)
- write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
+ write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
end
# Returns an array of all the attributes that have been made accessible to mass-assignment.
def accessible_attributes # :nodoc:
- read_inheritable_attribute("attr_accessible")
+ read_inheritable_attribute(:attr_accessible)
end
# Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
def attr_readonly(*attributes)
- write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
- read_inheritable_attribute("attr_readonly")
+ read_inheritable_attribute(:attr_readonly)
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -1002,7 +995,7 @@ module ActiveRecord #:nodoc:
# Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
def serialized_attributes
- read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
+ read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
end
@@ -1223,11 +1216,46 @@ module ActiveRecord #:nodoc:
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
+ def self_and_descendents_from_active_record#nodoc:
+ klass = self
+ classes = [klass]
+ while klass != klass.base_class
+ classes << klass = klass.superclass
+ end
+ classes
+ rescue
+ # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
+ # Appearantly the method base_class causes some trouble.
+ # It now works for sure.
+ [self]
+ end
+
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
# Person.human_attribute_name("first_name") # => "First name"
- # Deprecated in favor of just calling "first_name".humanize
- def human_attribute_name(attribute_key_name) #:nodoc:
- attribute_key_name.humanize
+ # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
+ # module now.
+ # Specify +options+ with additional translating options.
+ def human_attribute_name(attribute_key_name, options = {})
+ defaults = self_and_descendents_from_active_record.map do |klass|
+ :"#{klass.name.underscore}.#{attribute_key_name}"
+ end
+ defaults << options[:default] if options[:default]
+ defaults.flatten!
+ defaults << attribute_key_name.humanize
+ options[:count] ||= 1
+ I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
+ end
+
+ # Transform the modelname into a more humane format, using I18n.
+ # Defaults to the basic humanize method.
+ # Default scope of the translation is activerecord.models
+ # Specify +options+ with additional translating options.
+ def human_name(options = {})
+ defaults = self_and_descendents_from_active_record.map do |klass|
+ :"#{klass.name.underscore}"
+ end
+ defaults << self.name.humanize
+ I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
end
# True if this isn't a concrete subclass needing a STI type condition.
@@ -1322,8 +1350,8 @@ module ActiveRecord #:nodoc:
end
def respond_to?(method_id, include_private = false)
- if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
- return true if all_attributes_exists?(extract_attribute_names_from_match(match))
+ if match = DynamicFinderMatch.match(method_id)
+ return true if all_attributes_exists?(match.attribute_names)
end
super
end
@@ -1517,7 +1545,7 @@ module ActiveRecord #:nodoc:
sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
- add_joins!(sql, options, scope)
+ add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
@@ -1533,6 +1561,22 @@ module ActiveRecord #:nodoc:
(safe_to_array(first) + safe_to_array(second)).uniq
end
+ def merge_joins(first, second)
+ if first.is_a?(String) && second.is_a?(String)
+ "#{first} #{second}"
+ elsif first.is_a?(String) || second.is_a?(String)
+ if first.is_a?(String)
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, second, nil)
+ "#{first} #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join}"
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, first, nil)
+ "#{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} #{second}"
+ end
+ else
+ (safe_to_array(first) + safe_to_array(second)).uniq
+ end
+ end
+
# Object#to_a is deprecated, though it does have the desired behavior
def safe_to_array(o)
case o
@@ -1588,16 +1632,15 @@ module ActiveRecord #:nodoc:
end
# The optional scope argument is for the current <tt>:find</tt> scope.
- def add_joins!(sql, options, scope = :auto)
+ def add_joins!(sql, joins, scope = :auto)
scope = scope(:find) if :auto == scope
- [(scope && scope[:joins]), options[:joins]].each do |join|
- case join
- when Symbol, Hash, Array
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
- sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
- else
- sql << " #{join} "
- end
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ case merged_joins
+ when Symbol, Hash, Array
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
+ when String
+ sql << " #{merged_joins} "
end
end
@@ -1612,10 +1655,11 @@ module ActiveRecord #:nodoc:
sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
end
- def type_condition
+ def type_condition(table_alias=nil)
+ quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
- type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
- condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
+ type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
+ condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
end
" (#{type_condition}) "
@@ -1641,88 +1685,67 @@ module ActiveRecord #:nodoc:
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
# attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments)
- if match = matches_dynamic_finder?(method_id)
- finder = determine_finder(match)
-
- attribute_names = extract_attribute_names_from_match(match)
+ if match = DynamicFinderMatch.match(method_id)
+ attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
-
- self.class_eval %{
- def self.#{method_id}(*args)
- options = args.extract_options!
- attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
- finder_options = { :conditions => attributes }
- validate_find_options(options)
- set_readonly_option!(options)
-
- if options[:conditions]
- with_scope(:find => finder_options) do
- ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+ if match.finder?
+ finder = match.finder
+ bang = match.bang?
+ self.class_eval %{
+ def self.#{method_id}(*args)
+ options = args.extract_options!
+ attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ finder_options = { :conditions => attributes }
+ validate_find_options(options)
+ set_readonly_option!(options)
+
+ #{'result = ' if bang}if options[:conditions]
+ with_scope(:find => finder_options) do
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+ end
+ else
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
end
- else
- ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
- end
- end
- }, __FILE__, __LINE__
- send(method_id, *arguments)
- elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
- instantiator = determine_instantiator(match)
- attribute_names = extract_attribute_names_from_match(match)
- super unless all_attributes_exists?(attribute_names)
-
- self.class_eval %{
- def self.#{method_id}(*args)
- guard_protected_attributes = false
-
- if args[0].is_a?(Hash)
- guard_protected_attributes = true
- attributes = args[0].with_indifferent_access
- find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
- else
- find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ #{'result || raise(RecordNotFound)' if bang}
end
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ elsif match.instantiator?
+ instantiator = match.instantiator
+ self.class_eval %{
+ def self.#{method_id}(*args)
+ guard_protected_attributes = false
+
+ if args[0].is_a?(Hash)
+ guard_protected_attributes = true
+ attributes = args[0].with_indifferent_access
+ find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
+ else
+ find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ end
- options = { :conditions => find_attributes }
- set_readonly_option!(options)
+ options = { :conditions => find_attributes }
+ set_readonly_option!(options)
- record = find_initial(options)
+ record = find_initial(options)
- if record.nil?
- record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
- #{'yield(record) if block_given?'}
- #{'record.save' if instantiator == :create}
- record
- else
- record
+ if record.nil?
+ record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
+ #{'yield(record) if block_given?'}
+ #{'record.save' if instantiator == :create}
+ record
+ else
+ record
+ end
end
- end
- }, __FILE__, __LINE__
- send(method_id, *arguments)
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ end
else
super
end
end
- def matches_dynamic_finder?(method_id)
- /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
- end
-
- def matches_dynamic_finder_with_initialize_or_create?(method_id)
- /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
- end
-
- def determine_finder(match)
- match.captures.first == 'all_by' ? :find_every : :find_initial
- end
-
- def determine_instantiator(match)
- match.captures.first == 'initialize' ? :new : :create
- end
-
- def extract_attribute_names_from_match(match)
- match.captures.last.split('_and_')
- end
-
def construct_attributes_from_arguments(attribute_names, arguments)
attributes = {}
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
@@ -1752,7 +1775,7 @@ module ActiveRecord #:nodoc:
def attribute_condition(argument)
case argument
when nil then "IS ?"
- when Array, ActiveRecord::Associations::AssociationCollection then "IN (?)"
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "IN (?)"
when Range then "BETWEEN ? AND ?"
else "= ?"
end
@@ -1867,6 +1890,8 @@ module ActiveRecord #:nodoc:
hash[method][key] = merge_conditions(params[key], hash[method][key])
elsif key == :include && merge
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
+ elsif key == :joins && merge
+ hash[method][key] = merge_joins(params[key], hash[method][key])
else
hash[method][key] = hash[method][key] || params[key]
end
@@ -1914,22 +1939,11 @@ module ActiveRecord #:nodoc:
end
end
- def thread_safe_scoped_methods #:nodoc:
+ def scoped_methods #:nodoc:
scoped_methods = (Thread.current[:scoped_methods] ||= {})
scoped_methods[self] ||= []
end
- def single_threaded_scoped_methods #:nodoc:
- @scoped_methods ||= []
- end
-
- # pick up the correct scoped_methods version from @@allow_concurrency
- if @@allow_concurrency
- alias_method :scoped_methods, :thread_safe_scoped_methods
- else
- alias_method :scoped_methods, :single_threaded_scoped_methods
- end
-
def current_scoped_methods #:nodoc:
scoped_methods.last
end
@@ -2594,11 +2608,14 @@ module ActiveRecord #:nodoc:
end
def convert_number_column_value(value)
- case value
- when FalseClass; 0
- when TrueClass; 1
- when ''; nil
- else value
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
end
end
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index e765b46cc2..a675af4787 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -186,11 +186,17 @@ module ActiveRecord
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "
end
+
+ joins = ""
+ add_joins!(joins, options[:joins], scope)
+
if merged_includes.any?
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
end
- add_joins!(sql, options, scope)
+
+ sql << joins unless joins.blank?
+
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -260,7 +266,14 @@ module ActiveRecord
# column_alias_for("count(*)") # => "count_all"
# column_alias_for("count", "id") # => "count_id"
def column_alias_for(*keys)
- connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
+ table_name = keys.join(' ')
+ table_name.downcase!
+ table_name.gsub!(/\*/, 'all')
+ table_name.gsub!(/\W+/, ' ')
+ table_name.strip!
+ table_name.gsub!(/ +/, '_')
+
+ connection.table_alias_for(table_name)
end
def column_for(field)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index d99e183f9e..dd7ae51096 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -172,16 +172,15 @@ module ActiveRecord
#
# == Transactions
#
- # The entire callback chain for +save+ and +destroy+ runs within their transaction, including
- # the <tt>after_*</tt> hooks. Cancellation does not trigger a rollback. To rollback
- # the transaction just raise an exception the same way you do for regular transactions.
- #
- # Note though that such an exception bypasses the regular call chain in Active Record:
- # If ActiveRecord::Rollback is raised both +save+ and +destroy+ return +nil+. On the other
- # hand <tt>save!</tt> does *not* raise ActiveRecord::RecordNotSaved, and does not raise
- # anything else for that matter, <tt>save!</tt> just returns +nil+ in that case.
- # If any other exception is raised it goes up until it reaches the caller, no matter
- # which one of the three actions was being performed.
+ # The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
+ # within a transaction. That includes <tt>after_*</tt> hooks. If everything
+ # goes fine a COMMIT is executed once the chain has been completed.
+ #
+ # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
+ # can also trigger a ROLLBACK raising an exception in any of the callbacks,
+ # including <tt>after_*</tt> hooks. Note, however, that in that case the client
+ # needs to be aware of it because an ordinary +save+ will raise such exception
+ # instead of quietly returning +false+.
module Callbacks
CALLBACKS = %w(
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
new file mode 100644
index 0000000000..838b0434b0
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -0,0 +1,281 @@
+require 'monitor'
+require 'set'
+
+module ActiveRecord
+ # Raised when a connection could not be obtained within the connection
+ # acquisition timeout period.
+ class ConnectionTimeoutError < ConnectionNotEstablished
+ end
+
+ module ConnectionAdapters
+ # Connection pool base class for managing ActiveRecord database
+ # connections.
+ #
+ # Connections can be obtained and used from a connection pool in several
+ # ways:
+ #
+ # 1. Simply use ActiveRecord::Base.connection as with ActiveRecord 2.1 and
+ # earlier (pre-connection-pooling). Eventually, when you're done with
+ # the connection(s) and wish it to be returned to the pool, you call
+ # ActiveRecord::Base.clear_active_connections!. This will be the
+ # default behavior for ActiveRecord when used in conjunction with
+ # ActionPack's request handling cycle.
+ # 2. Manually check out a connection from the pool with
+ # ActiveRecord::Base.connection_pool.checkout. You are responsible for
+ # returning this connection to the pool when finished by calling
+ # ActiveRecord::Base.connection_pool.checkin(connection).
+ # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
+ # obtains a connection, yields it as the sole argument to the block,
+ # and returns it to the pool after the block completes.
+ #
+ # There are two connection-pooling-related options that you can add to
+ # your database connection configuration:
+ #
+ # * +pool+: number indicating size of connection pool (default 5)
+ # * +wait_timeout+: number of seconds to block and wait for a connection
+ # before giving up and raising a timeout error (default 5 seconds).
+ class ConnectionPool
+ delegate :verification_timeout, :to => "::ActiveRecord::Base"
+ attr_reader :spec
+
+ def initialize(spec)
+ @spec = spec
+ # The cache of reserved connections mapped to threads
+ @reserved_connections = {}
+ # The mutex used to synchronize pool access
+ @connection_mutex = Monitor.new
+ @queue = @connection_mutex.new_cond
+ # default 5 second timeout
+ @timeout = spec.config[:wait_timeout] || 5
+ # default max pool size to 5
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @connections = []
+ @checked_out = []
+ end
+
+ # Retrieve the connection associated with the current thread, or call
+ # #checkout to obtain one if necessary.
+ #
+ # #connection can be called any number of times; the connection is
+ # held in a hash keyed by the thread id.
+ def connection
+ if conn = @reserved_connections[current_connection_id]
+ conn.verify!(verification_timeout)
+ conn
+ else
+ @reserved_connections[current_connection_id] = checkout
+ end
+ end
+
+ # Signal that the thread is finished with the current connection.
+ # #release_connection releases the connection-thread association
+ # and returns the connection to the pool.
+ def release_connection
+ conn = @reserved_connections.delete(current_connection_id)
+ checkin conn if conn
+ end
+
+ # Reserve a connection, and yield it to a block. Ensure the connection is
+ # checked back in when finished.
+ def with_connection
+ conn = checkout
+ yield conn
+ ensure
+ checkin conn
+ end
+
+ # Returns true if a connection has already been opened.
+ def connected?
+ !@connections.empty?
+ end
+
+ # Disconnect all connections in the pool.
+ def disconnect!
+ @reserved_connections.each do |name,conn|
+ checkin conn
+ end
+ @reserved_connections = {}
+ @connections.each do |conn|
+ conn.disconnect!
+ end
+ @connections = []
+ end
+
+ # Clears the cache which maps classes
+ def clear_reloadable_connections!
+ @reserved_connections.each do |name, conn|
+ checkin conn
+ end
+ @reserved_connections = {}
+ @connections.each do |conn|
+ conn.disconnect! if conn.requires_reloading?
+ end
+ @connections = []
+ end
+
+ # Verify active connections and remove and disconnect connections
+ # associated with stale threads.
+ def verify_active_connections! #:nodoc:
+ clear_stale_cached_connections!
+ @connections.each do |connection|
+ connection.verify!(verification_timeout)
+ end
+ end
+
+ # Return any checked-out connections back to the pool by threads that
+ # are no longer alive.
+ def clear_stale_cached_connections!
+ remove_stale_cached_threads!(@reserved_connections) do |name, conn|
+ checkin conn
+ end
+ end
+
+ # Check-out a database connection from the pool.
+ def checkout
+ # Checkout an available connection
+ conn = @connection_mutex.synchronize do
+ if @checked_out.size < @connections.size
+ checkout_existing_connection
+ elsif @connections.size < @size
+ checkout_new_connection
+ end
+ end
+ return conn if conn
+
+ # No connections available; wait for one
+ @connection_mutex.synchronize do
+ if @queue.wait(@timeout)
+ checkout_existing_connection
+ else
+ raise ConnectionTimeoutError, "could not obtain a database connection in a timely fashion"
+ end
+ end
+ end
+
+ # Check-in a database connection back into the pool.
+ def checkin(conn)
+ @connection_mutex.synchronize do
+ conn.run_callbacks :checkin
+ @checked_out.delete conn
+ @queue.signal
+ end
+ end
+
+ synchronize :clear_reloadable_connections!, :verify_active_connections!,
+ :connected?, :disconnect!, :with => :@connection_mutex
+
+ private
+ def new_connection
+ config = spec.config.reverse_merge(:allow_concurrency => true)
+ ActiveRecord::Base.send(spec.adapter_method, config)
+ end
+
+ def current_connection_id #:nodoc:
+ Thread.current.object_id
+ end
+
+ # Remove stale threads from the cache.
+ def remove_stale_cached_threads!(cache, &block)
+ keys = Set.new(cache.keys)
+
+ Thread.list.each do |thread|
+ keys.delete(thread.object_id) if thread.alive?
+ end
+ keys.each do |key|
+ next unless cache.has_key?(key)
+ block.call(key, cache[key])
+ cache.delete(key)
+ end
+ end
+
+ def checkout_new_connection
+ c = new_connection
+ @connections << c
+ checkout_and_verify(c)
+ end
+
+ def checkout_existing_connection
+ c = (@connections - @checked_out).first
+ checkout_and_verify(c)
+ end
+
+ def checkout_and_verify(c)
+ c.run_callbacks :checkout
+ c.verify!(verification_timeout)
+ @checked_out << c
+ c
+ end
+ end
+
+ class ConnectionHandler
+ def initialize(pools = {})
+ @connection_pools = pools
+ end
+
+ def connection_pools
+ @connection_pools ||= {}
+ end
+
+ def establish_connection(name, spec)
+ @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
+ end
+
+ # Returns any connections in use by the current thread back to the pool,
+ # and also returns connections to the pool cached by threads that are no
+ # longer alive.
+ def clear_active_connections!
+ @connection_pools.each_value do |pool|
+ pool.release_connection
+ pool.clear_stale_cached_connections!
+ end
+ end
+
+ # Clears the cache which maps classes
+ def clear_reloadable_connections!
+ @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
+ end
+
+ def clear_all_connections!
+ @connection_pools.each_value {|pool| pool.disconnect! }
+ end
+
+ # Verify active connections.
+ def verify_active_connections! #:nodoc:
+ @connection_pools.each_value {|pool| pool.verify_active_connections! }
+ end
+
+ # Locate the connection of the nearest super class. This can be an
+ # active or defined connection: if it is the latter, it will be
+ # opened and set as the active connection for the class it was defined
+ # for (not necessarily the current class).
+ def retrieve_connection(klass) #:nodoc:
+ pool = retrieve_connection_pool(klass)
+ (pool && pool.connection) or raise ConnectionNotEstablished
+ end
+
+ # Returns true if a connection that's accessible to this class has
+ # already been opened.
+ def connected?(klass)
+ retrieve_connection_pool(klass).connected?
+ end
+
+ # Remove the connection for this class. This will close the active
+ # connection and the defined connection (if they exist). The result
+ # can be used as an argument for establish_connection, for easily
+ # re-establishing the connection.
+ def remove_connection(klass)
+ pool = @connection_pools[klass.name]
+ @connection_pools.delete_if { |key, value| value == pool }
+ pool.disconnect! if pool
+ pool.spec.config if pool
+ end
+
+ def retrieve_connection_pool(klass)
+ pool = @connection_pools[klass.name]
+ return pool if pool
+ return nil if ActiveRecord::Base == klass
+ retrieve_connection_pool klass.superclass
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 2a8807fb78..417a333aab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -1,5 +1,3 @@
-require 'set'
-
module ActiveRecord
class Base
class ConnectionSpecification #:nodoc:
@@ -14,158 +12,9 @@ module ActiveRecord
cattr_accessor :verification_timeout, :instance_writer => false
@@verification_timeout = 0
- # The class -> [adapter_method, config] map
- @@defined_connections = {}
-
- # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
- @@active_connections = {}
-
- class << self
- # Retrieve the connection cache.
- def thread_safe_active_connections #:nodoc:
- @@active_connections[Thread.current.object_id] ||= {}
- end
-
- def single_threaded_active_connections #:nodoc:
- @@active_connections
- end
-
- # pick up the right active_connection method from @@allow_concurrency
- if @@allow_concurrency
- alias_method :active_connections, :thread_safe_active_connections
- else
- alias_method :active_connections, :single_threaded_active_connections
- end
-
- # set concurrency support flag (not thread safe, like most of the methods in this file)
- def allow_concurrency=(threaded) #:nodoc:
- logger.debug "allow_concurrency=#{threaded}" if logger
- return if @@allow_concurrency == threaded
- clear_all_cached_connections!
- @@allow_concurrency = threaded
- method_prefix = threaded ? "thread_safe" : "single_threaded"
- sing = (class << self; self; end)
- [:active_connections, :scoped_methods].each do |method|
- sing.send(:alias_method, method, "#{method_prefix}_#{method}")
- end
- log_connections if logger
- end
-
- def active_connection_name #:nodoc:
- @active_connection_name ||=
- if active_connections[name] || @@defined_connections[name]
- name
- elsif self == ActiveRecord::Base
- nil
- else
- superclass.active_connection_name
- end
- end
-
- def clear_active_connection_name #:nodoc:
- @active_connection_name = nil
- subclasses.each { |klass| klass.clear_active_connection_name }
- end
-
- # Returns the connection currently associated with the class. This can
- # also be used to "borrow" the connection to do database work unrelated
- # to any of the specific Active Records.
- def connection
- if defined?(@active_connection_name) && (conn = active_connections[@active_connection_name])
- conn
- else
- # retrieve_connection sets the cache key.
- conn = retrieve_connection
- active_connections[@active_connection_name] = conn
- end
- end
-
- # Clears the cache which maps classes to connections.
- def clear_active_connections!
- clear_cache!(@@active_connections) do |name, conn|
- conn.disconnect!
- end
- end
-
- # Clears the cache which maps classes
- def clear_reloadable_connections!
- if @@allow_concurrency
- # With concurrent connections @@active_connections is
- # a hash keyed by thread id.
- @@active_connections.each do |thread_id, conns|
- conns.each do |name, conn|
- if conn.requires_reloading?
- conn.disconnect!
- @@active_connections[thread_id].delete(name)
- end
- end
- end
- else
- @@active_connections.each do |name, conn|
- if conn.requires_reloading?
- conn.disconnect!
- @@active_connections.delete(name)
- end
- end
- end
- end
-
- # Verify active connections.
- def verify_active_connections! #:nodoc:
- if @@allow_concurrency
- remove_stale_cached_threads!(@@active_connections) do |name, conn|
- conn.disconnect!
- end
- end
-
- active_connections.each_value do |connection|
- connection.verify!(@@verification_timeout)
- end
- end
-
- private
- def clear_cache!(cache, thread_id = nil, &block)
- if cache
- if @@allow_concurrency
- thread_id ||= Thread.current.object_id
- thread_cache, cache = cache, cache[thread_id]
- return unless cache
- end
-
- cache.each(&block) if block_given?
- cache.clear
- end
- ensure
- if thread_cache && @@allow_concurrency
- thread_cache.delete(thread_id)
- end
- end
-
- # Remove stale threads from the cache.
- def remove_stale_cached_threads!(cache, &block)
- stale = Set.new(cache.keys)
-
- Thread.list.each do |thread|
- stale.delete(thread.object_id) if thread.alive?
- end
-
- stale.each do |thread_id|
- clear_cache!(cache, thread_id, &block)
- end
- end
-
- def clear_all_cached_connections!
- if @@allow_concurrency
- @@active_connections.each_value do |connection_hash_for_thread|
- connection_hash_for_thread.each_value {|conn| conn.disconnect! }
- connection_hash_for_thread.clear
- end
- else
- @@active_connections.each_value {|conn| conn.disconnect! }
- end
- @@active_connections.clear
- end
- end
+ # The connection handler
+ cattr_accessor :connection_handler, :instance_writer => false
+ @@connection_handler = ConnectionAdapters::ConnectionHandler.new
# Returns the connection currently associated with the class. This can
# also be used to "borrow" the connection to do database work that isn't
@@ -208,9 +57,7 @@ module ActiveRecord
raise AdapterNotSpecified unless defined? RAILS_ENV
establish_connection(RAILS_ENV)
when ConnectionSpecification
- clear_active_connection_name
- @active_connection_name = name
- @@defined_connections[name] = spec
+ @@connection_handler.establish_connection(name, spec)
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
@@ -243,67 +90,42 @@ module ActiveRecord
end
end
- # Locate the connection of the nearest super class. This can be an
- # active or defined connection: if it is the latter, it will be
- # opened and set as the active connection for the class it was defined
- # for (not necessarily the current class).
- def self.retrieve_connection #:nodoc:
- # Name is nil if establish_connection hasn't been called for
- # some class along the inheritance chain up to AR::Base yet.
- if name = active_connection_name
- if conn = active_connections[name]
- # Verify the connection.
- conn.verify!(@@verification_timeout)
- elsif spec = @@defined_connections[name]
- # Activate this connection specification.
- klass = name.constantize
- klass.connection = spec
- conn = active_connections[name]
- end
+ class << self
+ # Deprecated and no longer has any effect.
+ def allow_concurrency
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency has been deprecated and no longer has any effect. Please remove all references to allow_concurrency.")
end
- conn or raise ConnectionNotEstablished
- end
+ # Deprecated and no longer has any effect.
+ def allow_concurrency=(flag)
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_concurrency= has been deprecated and no longer has any effect. Please remove all references to allow_concurrency=.")
+ end
- # Returns true if a connection that's accessible to this class has already been opened.
- def self.connected?
- active_connections[active_connection_name] ? true : false
- end
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work unrelated
+ # to any of the specific Active Records.
+ def connection
+ retrieve_connection
+ end
- # Remove the connection for this class. This will close the active
- # connection and the defined connection (if they exist). The result
- # can be used as an argument for establish_connection, for easily
- # re-establishing the connection.
- def self.remove_connection(klass=self)
- spec = @@defined_connections[klass.name]
- konn = active_connections[klass.name]
- @@defined_connections.delete_if { |key, value| value == spec }
- active_connections.delete_if { |key, value| value == konn }
- konn.disconnect! if konn
- spec.config if spec
- end
+ def connection_pool
+ connection_handler.retrieve_connection_pool(self)
+ end
- # Set the connection for the class.
- def self.connection=(spec) #:nodoc:
- if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
- active_connections[name] = spec
- elsif spec.kind_of?(ConnectionSpecification)
- config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
- self.connection = self.send(spec.adapter_method, config)
- elsif spec.nil?
- raise ConnectionNotEstablished
- else
- establish_connection spec
+ def retrieve_connection
+ connection_handler.retrieve_connection(self)
end
- end
- # connection state logging
- def self.log_connections #:nodoc:
- if logger
- logger.info "Defined connections: #{@@defined_connections.inspect}"
- logger.info "Active connections: #{active_connections.inspect}"
- logger.info "Active connection name: #{@active_connection_name}"
+ def connected?
+ connection_handler.connected?(self)
end
+
+ def remove_connection(klass = self)
+ connection_handler.remove_connection(klass)
+ end
+
+ delegate :clear_active_connections!, :clear_reloadable_connections!,
+ :clear_all_connections!,:verify_active_connections!, :to => :connection_handler
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 2afd6064ad..2fc50b9bfa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -4,7 +4,6 @@ module ActiveRecord
class << self
def included(base)
base.class_eval do
- attr_accessor :query_cache_enabled
alias_method_chain :columns, :query_cache
alias_method_chain :select_all, :query_cache
end
@@ -16,7 +15,7 @@ module ActiveRecord
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__
def #{method_name}_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled
+ clear_query_cache if query_cache_enabled
#{method_name}_without_query_dirty(*args)
end
@@ -26,22 +25,38 @@ module ActiveRecord
end
end
+ def query_cache_enabled
+ Thread.current['query_cache_enabled']
+ end
+
+ def query_cache_enabled=(flag)
+ Thread.current['query_cache_enabled'] = flag
+ end
+
+ def query_cache
+ Thread.current['query_cache']
+ end
+
+ def query_cache=(cache)
+ Thread.current['query_cache'] = cache
+ end
+
# Enable the query cache within the block.
def cache
- old, @query_cache_enabled = @query_cache_enabled, true
- @query_cache ||= {}
+ old, self.query_cache_enabled = query_cache_enabled, true
+ self.query_cache ||= {}
yield
ensure
clear_query_cache
- @query_cache_enabled = old
+ self.query_cache_enabled = old
end
# Disable the query cache within the block.
def uncached
- old, @query_cache_enabled = @query_cache_enabled, false
+ old, self.query_cache_enabled = query_cache_enabled, false
yield
ensure
- @query_cache_enabled = old
+ self.query_cache_enabled = old
end
# Clears the query cache.
@@ -51,11 +66,11 @@ module ActiveRecord
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
- @query_cache.clear if @query_cache
+ query_cache.clear if query_cache
end
def select_all_with_query_cache(*args)
- if @query_cache_enabled
+ if query_cache_enabled
cache_sql(args.first) { select_all_without_query_cache(*args) }
else
select_all_without_query_cache(*args)
@@ -63,8 +78,8 @@ module ActiveRecord
end
def columns_with_query_cache(*args)
- if @query_cache_enabled
- @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
+ if query_cache_enabled
+ query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
else
columns_without_query_cache(*args)
end
@@ -73,11 +88,11 @@ module ActiveRecord
private
def cache_sql(sql)
result =
- if @query_cache.has_key?(sql)
+ if query_cache.has_key?(sql)
log_info(sql, "CACHE", 0.0)
- @query_cache[sql]
+ query_cache[sql]
else
- @query_cache[sql] = yield
+ query_cache[sql] = yield
end
if Array === result
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 31d6c7942c..75032efe57 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -138,7 +138,11 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- TRUE_VALUES.include?(value)
+ if value.is_a?(String) && value.blank?
+ nil
+ else
+ TRUE_VALUES.include?(value)
+ end
end
# convert something to a BigDecimal
@@ -443,9 +447,10 @@ module ActiveRecord
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
- def timestamps
- column(:created_at, :datetime)
- column(:updated_at, :datetime)
+ def timestamps(*args)
+ options = args.extract_options!
+ column(:created_at, :datetime, options)
+ column(:updated_at, :datetime, options)
end
def references(*args)
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 0f60a91ef1..bececf82a0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -384,12 +384,8 @@ module ActiveRecord
def add_column_options!(sql, options) #:nodoc:
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
# 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"
- else
- sql << " NULL"
- end
+ if options[:null] == false
+ sql << " NOT NULL"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 47dbf5a5f3..005be9d72f 100644..100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -7,6 +7,7 @@ require 'active_record/connection_adapters/abstract/schema_definitions'
require 'active_record/connection_adapters/abstract/schema_statements'
require 'active_record/connection_adapters/abstract/database_statements'
require 'active_record/connection_adapters/abstract/quoting'
+require 'active_record/connection_adapters/abstract/connection_pool'
require 'active_record/connection_adapters/abstract/connection_specification'
require 'active_record/connection_adapters/abstract/query_cache'
@@ -24,6 +25,9 @@ module ActiveRecord
class AbstractAdapter
include Quoting, DatabaseStatements, SchemaStatements
include QueryCache
+ include ActiveSupport::Callbacks
+ define_callbacks :checkout, :checkin
+ checkout :reset!
@@row_even = true
def initialize(connection, logger = nil) #:nodoc:
@@ -51,6 +55,13 @@ module ActiveRecord
true
end
+ # Does this adapter support DDL rollbacks in transactions? That is, would
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
+ # SQL Server, and others support this. MySQL and others do not.
+ def supports_ddl_transactions?
+ false
+ end
+
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
@@ -95,14 +106,25 @@ module ActiveRecord
@active = false
end
+ # Reset the state of this connection, directing the DBMS to clear
+ # transactions and other connection-related server-side state. Usually a
+ # database-dependent operation; the default method simply executes a
+ # ROLLBACK and swallows any exceptions which is probably not enough to
+ # ensure the connection is clean.
+ def reset!
+ silence_stderr do # postgres prints on stderr when you do this w/o a txn
+ execute "ROLLBACK" rescue nil
+ end
+ end
+
# Returns true if its safe to reload the connection between requests for development mode.
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
false
end
- # Lazily verify this connection, calling <tt>active?</tt> only if it hasn't
- # been called for +timeout+ seconds.
+ # Lazily verify this connection, calling <tt>active?</tt> only if it
+ # hasn't been called for +timeout+ seconds.
def verify!(timeout)
now = Time.now.to_i
if (now - @last_verification) > timeout
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 204ebaa2e2..14c76ac455 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -280,6 +280,16 @@ module ActiveRecord
@connection.close rescue nil
end
+ def reset!
+ if @connection.respond_to?(:change_user)
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
+ # reset the connection is to change the user to the same user.
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
+ configure_connection
+ else
+ super
+ end
+ end
# DATABASE STATEMENTS ======================================
@@ -529,7 +539,11 @@ module ActiveRecord
end
@connection.real_connect(*@connection_options)
+ configure_connection
+ end
+ def configure_connection
+ encoding = @config[:encoding]
execute("SET NAMES '#{encoding}'") if encoding
# By default, MySQL 'where id is null' selects the last inserted id.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 856435517a..0c2532f21d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -23,8 +23,8 @@ module ActiveRecord
config = config.symbolize_keys
host = config[:host]
port = config[:port] || 5432
- username = config[:username].to_s
- password = config[:password].to_s
+ username = config[:username].to_s if config[:username]
+ password = config[:password].to_s if config[:password]
if config.has_key?(:database)
database = config[:database]
@@ -335,6 +335,10 @@ module ActiveRecord
postgresql_version >= 80200
end
+ def supports_ddl_transactions?
+ true
+ end
+
# Returns the configured supported identifier length supported by PostgreSQL,
# or report the default of 63 on PostgreSQL 7.x.
def table_alias_length
@@ -376,7 +380,7 @@ module ActiveRecord
# There are some incorrectly compiled postgres drivers out there
# that don't define PGconn.escape.
self.class.instance_eval do
- undef_method(:quote_string)
+ remove_method(:quote_string)
end
end
quote_string(s)
@@ -534,13 +538,13 @@ module ActiveRecord
option_string = options.symbolize_keys.sum do |key, value|
case key
when :owner
- " OWNER = '#{value}'"
+ " OWNER = \"#{value}\""
when :template
- " TEMPLATE = #{value}"
+ " TEMPLATE = \"#{value}\""
when :encoding
" ENCODING = '#{value}'"
when :tablespace
- " TABLESPACE = #{value}"
+ " TABLESPACE = \"#{value}\""
when :connection_limit
" CONNECTION LIMIT = #{value}"
else
@@ -761,7 +765,8 @@ module ActiveRecord
begin
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => e
+ raise e if postgresql_version > 80000
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
begin
begin_db_transaction
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index 63bf8c8f5b..7e246e62ca 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -123,7 +123,10 @@ module ActiveRecord
attr = attr.to_s
# The attribute already has an unsaved change.
- unless changed_attributes.include?(attr)
+ if changed_attributes.include?(attr)
+ old = changed_attributes[attr]
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ else
old = clone_attribute_value(:read_attribute, attr)
changed_attributes[attr] = old if field_changed?(attr, old, value)
end
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
new file mode 100644
index 0000000000..b105b919f5
--- /dev/null
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -0,0 +1,40 @@
+module ActiveRecord
+ class DynamicFinderMatch
+ def self.match(method)
+ df_match = self.new(method)
+ df_match.finder ? df_match : nil
+ end
+
+ def initialize(method)
+ @finder = :find_initial
+ case method.to_s
+ when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
+ @finder = :find_every if $1 == 'all_by'
+ names = $2
+ when /^find_by_([_a-zA-Z]\w*)\!$/
+ @bang = true
+ names = $1
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
+ @instantiator = $1 == 'initialize' ? :new : :create
+ names = $2
+ else
+ @finder = nil
+ end
+ @attribute_names = names && names.split('_and_')
+ end
+
+ attr_reader :finder, :attribute_names, :instantiator
+
+ def finder?
+ !@finder.nil? && @instantiator.nil?
+ end
+
+ def instantiator?
+ @finder == :find_initial && !@instantiator.nil?
+ end
+
+ def bang?
+ @bang
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 622cfc3c3f..114141a646 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -955,7 +955,7 @@ module Test #:nodoc:
ActiveRecord::Base.connection.rollback_db_transaction
ActiveRecord::Base.connection.decrement_open_transactions
end
- ActiveRecord::Base.verify_active_connections!
+ ActiveRecord::Base.clear_active_connections!
end
private
diff --git a/activerecord/lib/active_record/locale/en-US.rb b/activerecord/lib/active_record/locale/en-US.rb
deleted file mode 100644
index b31e13ed3a..0000000000
--- a/activerecord/lib/active_record/locale/en-US.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-I18n.backend.store_translations :'en-US', {
- :active_record => {
- :error_messages => {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is {{count}} characters)",
- :too_short => "is too short (minimum is {{count}} characters)",
- :wrong_length => "is the wrong length (should be {{count}} characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than {{count}}",
- :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
- :equal_to => "must be equal to {{count}}",
- :less_than => "must be less than {{count}}",
- :less_than_or_equal_to => "must be less than or equal to {{count}}",
- :odd => "must be odd",
- :even => "must be even"
- }
- }
-} \ No newline at end of file
diff --git a/activerecord/lib/active_record/locale/en-US.yml b/activerecord/lib/active_record/locale/en-US.yml
new file mode 100644
index 0000000000..8148f31a81
--- /dev/null
+++ b/activerecord/lib/active_record/locale/en-US.yml
@@ -0,0 +1,33 @@
+en-US:
+ activerecord:
+ errors:
+ # The values :model, :attribute and :value are always available for interpolation
+ # The value :count is available when applicable. Can be used for pluralization.
+ messages:
+ inclusion: "is not included in the list"
+ exclusion: "is reserved"
+ invalid: "is invalid"
+ confirmation: "doesn't match confirmation"
+ accepted: "must be accepted"
+ empty: "can't be empty"
+ blank: "can't be blank"
+ too_long: "is too long (maximum is {{count}} characters)"
+ too_short: "is too short (minimum is {{count}} characters)"
+ wrong_length: "is the wrong length (should be {{count}} characters)"
+ taken: "has already been taken"
+ not_a_number: "is not a number"
+ greater_than: "must be greater than {{count}}"
+ greater_than_or_equal_to: "must be greater than or equal to {{count}}"
+ equal_to: "must be equal to {{count}}"
+ less_than: "must be less than {{count}}"
+ less_than_or_equal_to: "must be less than or equal to {{count}}"
+ odd: "must be odd"
+ even: "must be even"
+ # Append your own errors here or at the model/attributes scope.
+
+ models:
+ # Overrides default messages
+
+ attributes:
+ # Overrides model and default messages.
+
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index fd77f27b77..1d843fff28 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -406,11 +406,17 @@ module ActiveRecord
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
end
+ def get_all_versions
+ Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
+ end
+
def current_version
- version = Base.connection.select_values(
- "SELECT version FROM #{schema_migrations_table_name}"
- ).map(&:to_i).max rescue nil
- version || 0
+ sm_table = schema_migrations_table_name
+ if Base.connection.table_exists?(sm_table)
+ get_all_versions.max || 0
+ else
+ 0
+ end
end
def proper_table_name(name)
@@ -426,7 +432,7 @@ module ActiveRecord
end
def current_version
- self.class.current_version
+ migrated.last || 0
end
def current_migration
@@ -461,14 +467,22 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})"
# On our way up, we skip migrating the ones we've already migrated
- # On our way down, we skip reverting the ones we've never migrated
next if up? && migrated.include?(migration.version.to_i)
+ # On our way down, we skip reverting the ones we've never migrated
if down? && !migrated.include?(migration.version.to_i)
migration.announce 'never migrated, skipping'; migration.write
- else
- migration.migrate(@direction)
- record_version_state_after_migrating(migration.version)
+ next
+ end
+
+ begin
+ ddl_transaction do
+ migration.migrate(@direction)
+ record_version_state_after_migrating(migration.version)
+ end
+ rescue => e
+ canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
end
end
end
@@ -509,17 +523,19 @@ module ActiveRecord
end
def migrated
- sm_table = self.class.schema_migrations_table_name
- Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
+ @migrated_versions ||= self.class.get_all_versions
end
private
def record_version_state_after_migrating(version)
sm_table = self.class.schema_migrations_table_name
+ @migrated_versions ||= []
if down?
+ @migrated_versions.delete(version.to_i)
Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
else
+ @migrated_versions.push(version.to_i).sort!
Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
end
end
@@ -531,5 +547,14 @@ module ActiveRecord
def down?
@direction == :down
end
+
+ # Wrap the migration in a transaction only if supported by the adapter.
+ def ddl_transaction(&block)
+ if Base.connection.supports_ddl_transactions?
+ Base.transaction { block.call }
+ else
+ block.call
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index eb887ee550..83043c2c22 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -101,9 +101,9 @@ module ActiveRecord
class Scope
attr_reader :proxy_scope, :proxy_options
-
+ NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set
[].methods.each do |m|
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/
+ unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
delegate m, :to => :proxy_found
end
end
@@ -136,12 +136,16 @@ module ActiveRecord
end
end
+ def size
+ @found ? @found.length : count
+ end
+
def empty?
@found ? @found.empty? : count.zero?
end
- def respond_to?(method)
- super || @proxy_scope.respond_to?(method)
+ def respond_to?(method, include_private = false)
+ super || @proxy_scope.respond_to?(method, include_private)
end
def any?
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 3f74c03714..935b1939d8 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -109,7 +109,7 @@ module ActiveRecord
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
# and +other_aggregation+ has an options hash assigned to it.
def ==(other_aggregation)
- name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
+ other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
end
def sanitized_conditions #:nodoc:
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index ca5591ae35..ffaa41282f 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -37,7 +37,7 @@ module ActiveRecord
$queries_executed = []
yield
ensure
- assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
+ assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
end
def assert_no_queries(&block)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index d1bf26f331..970da701c7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -91,11 +91,11 @@ module ActiveRecord
end
def destroy_with_transactions #:nodoc:
- transaction { destroy_without_transactions }
+ with_transaction_returning_status(:destroy_without_transactions)
end
def save_with_transactions(perform_validation = true) #:nodoc:
- rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } }
+ rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, perform_validation) }
end
def save_with_transactions! #:nodoc:
@@ -118,5 +118,17 @@ module ActiveRecord
end
raise
end
+
+ # Executes +method+ within a transaction and captures its return value as a
+ # status flag. If the status is true the transaction is committed, otherwise
+ # a ROLLBACK is issued. In any case the status flag is returned.
+ def with_transaction_returning_status(method, *args)
+ status = nil
+ transaction do
+ status = send(method, *args)
+ raise ActiveRecord::Rollback unless status
+ end
+ status
+ end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index b8b695e529..577e30ec86 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -21,8 +21,8 @@ module ActiveRecord
class << self
def default_error_messages
- ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('active_record.error_messages').")
- I18n.translate 'active_record.error_messages'
+ ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').")
+ I18n.translate 'activerecord.errors.messages'
end
end
@@ -38,22 +38,24 @@ module ActiveRecord
add(:base, msg)
end
- # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
+ # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
- # If no +msg+ is supplied, "invalid" is assumed.
- def add(attribute, message = nil)
- message ||= I18n.translate :"active_record.error_messages.invalid"
+ # If no +messsage+ is supplied, :invalid is assumed.
+ # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
+ def add(attribute, message = nil, options = {})
+ message ||= :invalid
+ message = generate_message(attribute, message, options) if message.is_a?(Symbol)
@errors[attribute.to_s] ||= []
@errors[attribute.to_s] << message
- end
+ end
# Will add an error message to each of the attributes in +attributes+ that is empty.
def add_on_empty(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- is_empty = value.respond_to?("empty?") ? value.empty? : false
- add(attr, generate_message(attr, :empty, :default => custom_message)) unless !value.nil? && !is_empty
+ is_empty = value.respond_to?(:empty?) ? value.empty? : false
+ add(attr, :empty, :default => custom_message) unless !value.nil? && !is_empty
end
end
@@ -61,16 +63,51 @@ module ActiveRecord
def add_on_blank(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- add(attr, generate_message(attr, :blank, :default => custom_message)) if value.blank?
+ add(attr, :blank, :default => custom_message) if value.blank?
end
end
- def generate_message(attr, key, options = {})
- msgs = base_classes(@base.class).map{|klass| :"custom.#{klass.name.underscore}.#{attr}.#{key}"}
- msgs << options[:default] if options[:default]
- msgs << key
+ # Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
+ # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
+ # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
+ # default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
+ # translated attribute name and the value are available for interpolation.
+ #
+ # When using inheritence in your models, it will check all the inherited models too, but only if the model itself
+ # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
+ # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
+ #
+ # <ol>
+ # <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
+ # <li><tt>activerecord.errors.models.admin.blank</tt></li>
+ # <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
+ # <li><tt>activerecord.errors.models.user.blank</tt></li>
+ # <li><tt>activerecord.errors.messages.blank</tt></li>
+ # <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
+ # </ol>
+ def generate_message(attribute, message = :invalid, options = {})
+
+ message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
+
+ defaults = @base.class.self_and_descendents_from_active_record.map do |klass|
+ [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
+ :"models.#{klass.name.underscore}.#{message}" ]
+ end
+
+ defaults << options.delete(:default)
+ defaults = defaults.compact.flatten << :"messages.#{message}"
- I18n.t nil, options.merge(:default => msgs, :scope => [:active_record, :error_messages])
+ key = defaults.shift
+ value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
+
+ options = { :default => defaults,
+ :model => @base.class.human_name,
+ :attribute => @base.class.human_attribute_name(attribute.to_s),
+ :value => value,
+ :scope => [:activerecord, :errors]
+ }.merge(options)
+
+ I18n.translate(key, options)
end
# Returns true if the specified +attribute+ has errors associated with it.
@@ -166,9 +203,9 @@ module ActiveRecord
if attr == "base"
full_messages << message
else
- key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
- attr_name = I18n.translate(key, :locale => options[:locale], :default => @base.class.human_attribute_name(attr))
- full_messages << attr_name + " " + message
+ #key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
+ attr_name = @base.class.human_attribute_name(attr)
+ full_messages << attr_name + ' ' + message
end
end
end
@@ -219,16 +256,6 @@ module ActiveRecord
end
end
- protected
-
- # TODO maybe this should be on ActiveRecord::Base, maybe #self_and_descendents_from_active_record
- def base_classes(klass)
- classes = [klass]
- while klass != klass.base_class
- classes << klass = klass.superclass
- end
- classes
- end
end
@@ -398,8 +425,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
- message = record.errors.generate_message(attr_name, :confirmation, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :confirmation, :default => configuration[:message])
end
end
end
@@ -441,8 +467,7 @@ module ActiveRecord
validates_each(attr_names,configuration) do |record, attr_name, value|
unless value == configuration[:accept]
- message = record.errors.generate_message(attr_name, :accepted, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :accepted, :default => configuration[:message])
end
end
end
@@ -544,11 +569,9 @@ module ActiveRecord
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
- message = record.errors.generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
- record.errors.add(attr, message)
+ record.errors.add(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
elsif value.size > option_value.end
- message = record.errors.generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
- record.errors.add(attr, message)
+ record.errors.add(attr, :too_long, :default => options[:too_long], :count => option_value.end)
end
end
when :is, :minimum, :maximum
@@ -563,8 +586,7 @@ module ActiveRecord
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
key = message_options[option]
custom_message = options[:message] || options[key]
- message = record.errors.generate_message(attr, key, :default => custom_message, :count => option_value)
- record.errors.add(attr, message)
+ record.errors.add(attr, key, :default => custom_message, :count => option_value)
end
end
end
@@ -629,12 +651,11 @@ module ActiveRecord
if value.nil?
comparison_operator = "IS ?"
- else
+ elsif is_text_column
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
-
- if is_text_column
- value = value.to_s
- end
+ value = value.to_s
+ else
+ comparison_operator = "= ?"
end
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
@@ -644,7 +665,7 @@ module ActiveRecord
condition_params = [value]
else
condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
- condition_params = [value.downcase]
+ condition_params = [value.chars.downcase]
end
if scope = configuration[:scope]
@@ -662,8 +683,7 @@ module ActiveRecord
finder_class.with_exclusive_scope do
if finder_class.exists?([condition_sql, *condition_params])
- message = record.errors.generate_message(attr_name, :taken, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
end
end
end
@@ -701,8 +721,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless value.to_s =~ configuration[:with]
- message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
end
end
end
@@ -732,12 +751,11 @@ module ActiveRecord
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
unless enum.include?(value)
- message = record.errors.generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
end
end
end
@@ -767,12 +785,11 @@ module ActiveRecord
enum = configuration[:in] || configuration[:within]
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
validates_each(attr_names, configuration) do |record, attr_name, value|
if enum.include?(value)
- message = record.errors.generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
end
end
end
@@ -814,8 +831,7 @@ module ActiveRecord
validates_each(attr_names, configuration) do |record, attr_name, value|
unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
- message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
end
end
end
@@ -864,8 +880,7 @@ module ActiveRecord
if configuration[:only_integer]
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
- message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
raw_value = raw_value.to_i
@@ -873,8 +888,7 @@ module ActiveRecord
begin
raw_value = Kernel.Float(raw_value)
rescue ArgumentError, TypeError
- message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
next
end
end
@@ -883,12 +897,10 @@ module ActiveRecord
case option
when :odd, :even
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
- message = record.errors.generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
- record.errors.add(attr_name, message)
+ record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
end
else
- message = record.errors.generate_message(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
- record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
+ record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
end
end
end