aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/base.rb')
-rwxr-xr-xactiverecord/lib/active_record/base.rb98
1 files changed, 56 insertions, 42 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 0efccb66ee..9943a7014a 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -416,7 +416,7 @@ module ActiveRecord #:nodoc:
end
@@subclasses = {}
-
+
##
# :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
@@ -661,9 +661,8 @@ module ActiveRecord #:nodoc:
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end
-
# Returns true if a record exists in the table that matches the +id+ or
- # conditions given, or false otherwise. The argument can take four forms:
+ # conditions given, or false otherwise. The argument can take five forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -672,6 +671,7 @@ module ActiveRecord #:nodoc:
# (such as <tt>['color = ?', 'red']</tt>).
# * Hash - Finds the record that matches these +find+-style conditions
# (such as <tt>{:color => 'red'}</tt>).
+ # * No args - Returns false if the table is empty, true otherwise.
#
# For more information about specifying conditions as a Hash or Array,
# see the Conditions section in the introduction to ActiveRecord::Base.
@@ -685,7 +685,8 @@ module ActiveRecord #:nodoc:
# Person.exists?('5')
# Person.exists?(:name => "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
- def exists?(id_or_conditions)
+ # Person.exists?
+ def exists?(id_or_conditions = {})
connection.select_all(
construct_finder_sql(
:select => "#{quoted_table_name}.#{primary_key}",
@@ -735,12 +736,12 @@ module ActiveRecord #:nodoc:
# ==== Parameters
#
# * +id+ - This should be the id or an array of ids to be updated.
- # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
+ # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
#
# ==== Examples
#
# # Updating one record:
- # Person.update(15, { :user_name => 'Samuel', :group => 'expert' })
+ # Person.update(15, :user_name => 'Samuel', :group => 'expert')
#
# # Updating multiple records:
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
@@ -809,25 +810,28 @@ module ActiveRecord #:nodoc:
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
+ # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
+ # or validations.
#
# ==== Parameters
#
- # * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
- # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
+ # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
+ # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
#
- # # Update all billing objects with the 3 different attributes given
- # Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
+ # # Update all customers with the given attributes
+ # Customer.update_all :wants_email => true
#
- # # Update records that match our conditions
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
+ # # Update all books with 'Rails' in their title
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
#
- # # Update records that match our conditions but limit it to 5 ordered by date
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
- # :order => 'created_at', :limit => 5 )
+ # # Update all avatars migrated more than a week ago
+ # Avatar.update_all ['migrated_at = ?, Time.now.utc], ['migrated_at > ?', 1.week.ago]
+ #
+ # # Update all books that match our conditions, but limit it to 5 ordered by date
+ # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
@@ -884,7 +888,8 @@ module ActiveRecord #:nodoc:
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored.
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
+ # the number of rows affected.
#
# ==== Parameters
#
@@ -1000,7 +1005,6 @@ module ActiveRecord #:nodoc:
update_counters(id, counter_name => -1)
end
-
# Attributes named in this macro are protected from mass-assignment,
# such as <tt>new(attributes)</tt>,
# <tt>update_attributes(attributes)</tt>, or
@@ -1101,7 +1105,6 @@ module ActiveRecord #:nodoc:
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
end
-
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
# directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
# to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
@@ -1344,7 +1347,7 @@ module ActiveRecord #:nodoc:
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
- def self_and_descendents_from_active_record#nodoc:
+ def self_and_descendants_from_active_record#nodoc:
klass = self
classes = [klass]
while klass != klass.base_class
@@ -1364,7 +1367,7 @@ module ActiveRecord #:nodoc:
# 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|
+ defaults = self_and_descendants_from_active_record.map do |klass|
:"#{klass.name.underscore}.#{attribute_key_name}"
end
defaults << options[:default] if options[:default]
@@ -1379,7 +1382,7 @@ module ActiveRecord #:nodoc:
# 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|
+ defaults = self_and_descendants_from_active_record.map do |klass|
:"#{klass.name.underscore}"
end
defaults << self.name.humanize
@@ -1414,7 +1417,6 @@ module ActiveRecord #:nodoc:
end
end
-
def quote_value(value, column = nil) #:nodoc:
connection.quote(value,column)
end
@@ -1483,7 +1485,7 @@ module ActiveRecord #:nodoc:
elsif match = DynamicScopeMatch.match(method_id)
return true if all_attributes_exists?(match.attribute_names)
end
-
+
super
end
@@ -1534,7 +1536,7 @@ module ActiveRecord #:nodoc:
end
def reverse_sql_order(order_query)
- reversed_query = order_query.split(/,/).each { |s|
+ reversed_query = order_query.to_s.split(/,/).each { |s|
if s.match(/\s(asc|ASC)$/)
s.gsub!(/\s(asc|ASC)$/, ' DESC')
elsif s.match(/\s(desc|DESC)$/)
@@ -1687,7 +1689,7 @@ module ActiveRecord #:nodoc:
def construct_finder_sql(options)
scope = scope(:find)
sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
- sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
+ sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
add_joins!(sql, options[:joins], scope)
add_conditions!(sql, options[:conditions], scope)
@@ -1742,7 +1744,9 @@ module ActiveRecord #:nodoc:
scoped_order = scope[:order] if scope
if order
sql << " ORDER BY #{order}"
- sql << ", #{scoped_order}" if scoped_order
+ if scoped_order && scoped_order != order
+ sql << ", #{scoped_order}"
+ end
else
sql << " ORDER BY #{scoped_order}" if scoped_order
end
@@ -1751,12 +1755,12 @@ module ActiveRecord #:nodoc:
def add_group!(sql, group, having, scope = :auto)
if group
sql << " GROUP BY #{group}"
- sql << " HAVING #{having}" if having
+ sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having
else
scope = scope(:find) if :auto == scope
if scope && (scoped_group = scope[:group])
sql << " GROUP BY #{scoped_group}"
- sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
+ sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
end
end
end
@@ -1990,12 +1994,16 @@ module ActiveRecord #:nodoc:
attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
end
- def attribute_condition(argument)
+ def attribute_condition(quoted_column_name, argument)
case argument
- when nil then "IS ?"
- when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "IN (?)"
- when Range then "BETWEEN ? AND ?"
- else "= ?"
+ when nil then "#{quoted_column_name} IS ?"
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)"
+ when Range then if argument.exclude_end?
+ "#{quoted_column_name} >= ? AND #{quoted_column_name} < ?"
+ else
+ "#{quoted_column_name} BETWEEN ? AND ?"
+ end
+ else "#{quoted_column_name} = ?"
end
end
@@ -2007,7 +2015,6 @@ module ActiveRecord #:nodoc:
end
end
-
# Defines an "attribute" method (like +inheritance_column+ or
# +table_name+). A new (class) method will be created with the
# given name. If a value is specified, the new method will
@@ -2104,7 +2111,7 @@ module ActiveRecord #:nodoc:
end
# Merge scopings
- if action == :merge && current_scoped_methods
+ if [:merge, :reverse_merge].include?(action) && current_scoped_methods
method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
case hash[method]
when Hash
@@ -2126,7 +2133,11 @@ module ActiveRecord #:nodoc:
end
end
else
- hash[method] = hash[method].merge(params)
+ if action == :reverse_merge
+ hash[method] = hash[method].merge(params)
+ else
+ hash[method] = params.merge(hash[method])
+ end
end
else
hash[method] = params
@@ -2136,7 +2147,6 @@ module ActiveRecord #:nodoc:
end
self.scoped_methods << method_scoping
-
begin
yield
ensure
@@ -2167,7 +2177,7 @@ module ActiveRecord #:nodoc:
# Test whether the given method and optional key are scoped.
def scoped?(method, key = nil) #:nodoc:
if current_scoped_methods && (scope = current_scoped_methods[method])
- !key || scope.has_key?(key)
+ !key || !scope[key].nil?
end
end
@@ -2305,7 +2315,7 @@ module ActiveRecord #:nodoc:
table_name = connection.quote_table_name(table_name)
end
- "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
+ attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
else
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end
@@ -2742,7 +2752,6 @@ module ActiveRecord #:nodoc:
assign_multiparameter_attributes(multi_parameter_attributes)
end
-
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
def attributes
self.attribute_names.inject({}) do |attrs, name|
@@ -3136,7 +3145,12 @@ module ActiveRecord #:nodoc:
include Dirty
include Callbacks, Observing, Timestamp
include Associations, AssociationPreload, NamedScope
- include Aggregations, Transactions, Reflection, Calculations, Serialization
+
+ # AutosaveAssociation needs to be included before Transactions, because we want
+ # #save_with_autosave_associations to be wrapped inside a transaction.
+ include AutosaveAssociation, NestedAttributes
+
+ include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization
end
end