aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
authorlholden <lholden@yellowpages.com>2009-01-21 16:17:24 -0800
committerlholden <lholden@yellowpages.com>2009-01-21 16:17:24 -0800
commit19f8bb2808316dfca1f009538c3505ba144f614b (patch)
tree1ab9e925e83b988ed2a1858aa19236e2bcd79a60 /activerecord/lib/active_record
parentb8fadd708b9850a77e1f64038763fffcff502499 (diff)
parent73cc5f270a5c2a2eab76c6c02615fec608822494 (diff)
downloadrails-19f8bb2808316dfca1f009538c3505ba144f614b.tar.gz
rails-19f8bb2808316dfca1f009538c3505ba144f614b.tar.bz2
rails-19f8bb2808316dfca1f009538c3505ba144f614b.zip
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb12
-rwxr-xr-xactiverecord/lib/active_record/base.rb122
-rw-r--r--activerecord/lib/active_record/calculations.rb24
-rw-r--r--activerecord/lib/active_record/callbacks.rb7
-rw-r--r--activerecord/lib/active_record/dirty.rb4
-rw-r--r--activerecord/lib/active_record/locale/en.yml2
-rw-r--r--activerecord/lib/active_record/named_scope.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb8
9 files changed, 117 insertions, 68 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 86616abf52..8b51a38f48 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1531,14 +1531,14 @@ module ActiveRecord
association = send(reflection.name)
association.destroy unless association.nil?
end
- before_destroy method_name
+ after_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.delete unless association.nil?
end
- before_destroy method_name
+ after_destroy method_name
else
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 3d689098b5..a5cc3bf091 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
@@ -9,6 +9,14 @@ module ActiveRecord
create_record(attributes) { |record| insert_record(record, true) }
end
+ def columns
+ @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
+ end
+
+ def reset_column_information
+ @reflection.reset_column_information
+ end
+
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@@ -32,8 +40,6 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
-
attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
@@ -103,7 +109,7 @@ module ActiveRecord
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
# an id column. This will then overwrite the id column of the records coming back.
def finding_with_ambiguous_select?(select_clause)
- !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
+ !select_clause && columns.size != 2
end
private
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index cca012ed55..ebc0b7783f 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -327,7 +327,7 @@ module ActiveRecord #:nodoc:
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
#
# You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
- # descendent of a class not in the hierarchy. Example:
+ # descendant of a class not in the hierarchy. Example:
#
# class User < ActiveRecord::Base
# serialize :preferences, Hash
@@ -544,8 +544,9 @@ module ActiveRecord #:nodoc:
# * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
- # or named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s).
+ # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
+ # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
+ # or an array containing a mixture of both strings and named associations.
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
@@ -755,25 +756,26 @@ module ActiveRecord #:nodoc:
end
end
- # Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command
- # is executed on the database which means that no callbacks are fired off running this. This is an efficient method
- # of deleting records that don't need cleaning up after or other actions to be taken.
+ # Deletes the row with a primary key matching the +id+ argument, using a
+ # SQL +DELETE+ statement, and returns the number of rows deleted. Active
+ # Record objects are not instantiated, so the object's callbacks are not
+ # executed, including any <tt>:dependent</tt> association options or
+ # Observer methods.
#
- # Objects are _not_ instantiated with this method, and so +:dependent+ rules
- # defined on associations are not honered.
+ # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
#
- # ==== Parameters
- #
- # * +id+ - Can be either an Integer or an Array of Integers.
+ # Note: Although it is often much faster than the alternative,
+ # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
+ # your application that ensures referential integrity or performs other
+ # essential jobs.
#
# ==== Examples
#
- # # Delete a single object
+ # # Delete a single row
# Todo.delete(1)
#
- # # Delete multiple objects
- # todos = [1,2,3]
- # Todo.delete(todos)
+ # # Delete multiple rows
+ # Todo.delete([2,3,4])
def delete(id)
delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
end
@@ -849,25 +851,32 @@ module ActiveRecord #:nodoc:
connection.update(sql, "#{name} Update")
end
- # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
- # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
- # many records. If you want to simply delete records without worrying about dependent associations or
- # callbacks, use the much faster +delete_all+ method instead.
+ # Destroys the records matching +conditions+ by instantiating each
+ # record and calling its +destroy+ method. Each object's callbacks are
+ # executed (including <tt>:dependent</tt> association options and
+ # +before_destroy+/+after_destroy+ Observer methods). Returns the
+ # collection of objects that were destroyed; each will be frozen, to
+ # reflect that no changes should be made (since they can't be
+ # persisted).
+ #
+ # Note: Instantiation, callback execution, and deletion of each
+ # record can be time consuming when you're removing many records at
+ # once. It generates at least one SQL +DELETE+ query per record (or
+ # possibly more, to enforce your callbacks). If you want to delete many
+ # rows quickly, without concern for their associations or callbacks, use
+ # +delete_all+ instead.
#
# ==== Parameters
#
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
+ # * +conditions+ - A string, array, or hash that specifies which records
+ # to destroy. If omitted, all records are destroyed. See the
+ # Conditions section in the introduction to ActiveRecord::Base for
+ # more information.
#
- # ==== Example
+ # ==== Examples
#
# Person.destroy_all("last_login < '2004-04-04'")
- #
- # This loads and destroys each person one by one, including its dependent associations and before_ and
- # after_destroy callbacks.
- #
- # +conditions+ can be anything that +find+ also accepts:
- #
- # Person.destroy_all(:last_login => 6.hours.ago)
+ # Person.destroy_all(:status => "inactive")
def destroy_all(conditions = nil)
find(:all, :conditions => conditions).each { |object| object.destroy }
end
@@ -1802,15 +1811,13 @@ module ActiveRecord #:nodoc:
table_name
end
- # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
- # find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
- # respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]).
- #
- # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
- # is actually find_all_by_amount(amount, options).
+ # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
+ # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
+ # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
+ # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
#
- # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
- # or find_or_create_by_user_and_password(user, password).
+ # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
+ # is actually <tt>find_all_by_amount(amount, options)</tt>.
#
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
# are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
@@ -2032,7 +2039,11 @@ module ActiveRecord #:nodoc:
# end
#
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
- # <tt>:conditions</tt> and <tt>:include</tt> options in <tt>:find</tt>, which are merged.
+ # <tt>:conditions</tt>, <tt>:include</tt>, and <tt>:joins</tt> options in <tt>:find</tt>, which are merged.
+ #
+ # <tt>:joins</tt> options are uniqued so multiple scopes can join in the same table without table aliasing
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
+ # array of strings format for your joins.
#
# class Article < ActiveRecord::Base
# def self.find_with_scope
@@ -2156,7 +2167,7 @@ module ActiveRecord #:nodoc:
scoped_methods.last
end
- # Returns the class type of the record using the current module as a prefix. So descendents of
+ # Returns the class type of the record using the current module as a prefix. So descendants of
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
def compute_type(type_name)
modularized_name = type_name_with_module(type_name)
@@ -2169,7 +2180,8 @@ module ActiveRecord #:nodoc:
end
end
- # Returns the class descending directly from Active Record in the inheritance hierarchy.
+ # Returns the class descending directly from ActiveRecord::Base or an
+ # abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
if klass.superclass == Base || klass.superclass.abstract_class?
klass
@@ -2518,14 +2530,16 @@ module ActiveRecord #:nodoc:
create_or_update || raise(RecordNotSaved)
end
- # Deletes the record in the database and freezes this instance to reflect that no changes should
- # be made (since they can't be persisted).
+ # Deletes the record in the database and freezes this instance to
+ # reflect that no changes should be made (since they can't be
+ # persisted). Returns the frozen instance.
#
- # Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+
- # callbacks, nor will it enforce any association +:dependent+ rules.
- #
- # In addition to deleting this record, any defined +before_delete+ and +after_delete+
- # callbacks are run, and +:dependent+ rules defined on associations are run.
+ # The row is simply removed with a SQL +DELETE+ statement on the
+ # record's primary key, and no callbacks are executed.
+ #
+ # To enforce the object's +before_destroy+ and +after_destroy+
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
+ # options, use <tt>#destroy</tt>.
def delete
self.class.delete(id) unless new_record?
freeze
@@ -2726,7 +2740,19 @@ module ActiveRecord #:nodoc:
end
end
- # Format attributes nicely for inspect.
+ # Returns an <tt>#inspect</tt>-like string for the value of the
+ # attribute +attr_name+. String attributes are elided after 50
+ # characters, and Date and Time attributes are returned in the
+ # <tt>:db</tt> format. Other attributes return the value of
+ # <tt>#inspect</tt> without modification.
+ #
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
+ #
+ # person.attribute_for_inspect(:name)
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
+ #
+ # person.attribute_for_inspect(:created_at)
+ # # => '"2009-01-12 04:48:57"'
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
@@ -2855,7 +2881,7 @@ module ActiveRecord #:nodoc:
id
end
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
# set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
# Message class in that example.
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 65512d534a..b239c03284 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -48,30 +48,38 @@ module ActiveRecord
calculate(:count, *construct_count_options_from_args(*args))
end
- # Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options.
+ # Calculates the average value on a given column. The value is returned as
+ # a float, or +nil+ if there's no row. See +calculate+ for examples with
+ # options.
#
- # Person.average('age')
+ # Person.average('age') # => 35.8
def average(column_name, options = {})
calculate(:avg, column_name, options)
end
- # Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ # Calculates the minimum value on a given column. The value is returned
+ # with the same data type of the column, or +nil+ if there's no row. See
+ # +calculate+ for examples with options.
#
- # Person.minimum('age')
+ # Person.minimum('age') # => 7
def minimum(column_name, options = {})
calculate(:min, column_name, options)
end
- # Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ # Calculates the maximum value on a given column. The value is returned
+ # with the same data type of the column, or +nil+ if there's no row. See
+ # +calculate+ for examples with options.
#
- # Person.maximum('age')
+ # Person.maximum('age') # => 93
def maximum(column_name, options = {})
calculate(:max, column_name, options)
end
- # Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ # Calculates the sum of values on a given column. The value is returned
+ # with the same data type of the column, 0 if there's no row. See
+ # +calculate+ for examples with options.
#
- # Person.sum('age')
+ # Person.sum('age') # => 4562
def sum(column_name, options = {})
calculate(:sum, column_name, options)
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 42bfe34505..88958f4583 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -77,7 +77,7 @@ module ActiveRecord
#
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
# you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
- # when you want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
+ # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
#
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
# associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
@@ -219,8 +219,9 @@ module ActiveRecord
def after_save() end
def create_or_update_with_callbacks #:nodoc:
return false if callback(:before_save) == false
- result = create_or_update_without_callbacks
- callback(:after_save)
+ if result = create_or_update_without_callbacks
+ callback(:after_save)
+ end
result
end
private :create_or_update_with_callbacks
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index 4c899f58e5..4a2510aa63 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -151,8 +151,8 @@ module ActiveRecord
def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- if column.type == :integer && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 7e205435f7..bf8a71d236 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -37,7 +37,7 @@ en:
# blank: "This is a custom blank message for User login"
# Will define custom blank validation message for User model and
# custom blank validation message for login attribute of User model.
- models:
+ #models:
# Translate model names. Used in Model.human_name().
#models:
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 83043c2c22..989b2a1ec5 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -39,7 +39,7 @@ module ActiveRecord
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
- # All \scopes are available as class methods on the ActiveRecord::Base descendent upon which the \scopes were defined. But they are also available to
+ # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to
# <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index dbff4f24d6..1937abdc83 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -198,6 +198,14 @@ module ActiveRecord
end
end
+ def columns(tbl_name, log_msg)
+ @columns ||= klass.connection.columns(tbl_name, log_msg)
+ end
+
+ def reset_column_information
+ @columns = nil
+ end
+
def check_validity!
end