aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2008-10-05 22:16:26 +0100
committerPratik Naik <pratiknaik@gmail.com>2008-10-05 22:16:26 +0100
commita2932784bb71e72a78c32819ebd7ed2bed551e3e (patch)
tree99bfd589a48153e33f19ae72baa6e98f5708a9b8 /activerecord
parent4df45d86097efbeabceecfe53d8ea2da9ccbb107 (diff)
downloadrails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.tar.gz
rails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.tar.bz2
rails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.zip
Merge docrails
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/aggregations.rb4
-rw-r--r--activerecord/lib/active_record/association_preload.rb90
-rwxr-xr-xactiverecord/lib/active_record/associations.rb8
-rwxr-xr-xactiverecord/lib/active_record/base.rb150
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb10
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb49
-rw-r--r--activerecord/lib/active_record/transactions.rb87
-rw-r--r--activerecord/lib/active_record/validations.rb68
10 files changed, 469 insertions, 79 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 9eee7f2d98..1eefebb3b3 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -75,7 +75,7 @@ module ActiveRecord
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
- # customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
+ # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
# customer.balance > Money.new(10) # => true
# customer.balance == Money.new(20) # => true
# customer.balance < Money.new(5) # => false
@@ -99,7 +99,7 @@ module ActiveRecord
# relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
#
# It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
- # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchanged_to method that
+ # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchange_to method that
# returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
# changed through means other than the writer method.
#
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index c38b98258a..99e3973830 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -1,14 +1,88 @@
module ActiveRecord
+ # See ActiveRecord::AssociationPreload::ClassMethods for documentation.
module AssociationPreload #:nodoc:
def self.included(base)
base.extend(ClassMethods)
end
+ # Implements the details of eager loading of ActiveRecord associations.
+ # Application developers should not use this module directly.
+ #
+ # ActiveRecord::Base is extended with this module. The source code in
+ # ActiveRecord::Base references methods defined in this module.
+ #
+ # Note that 'eager loading' and 'preloading' are actually the same thing.
+ # However, there are two different eager loading strategies.
+ #
+ # The first one is by using table joins. This was only strategy available
+ # prior to Rails 2.1. Suppose that you have an Author model with columns
+ # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
+ # this strategy, ActiveRecord would try to retrieve all data for an author
+ # and all of its books via a single query:
+ #
+ # SELECT * FROM authors
+ # LEFT OUTER JOIN books ON authors.id = books.id
+ # WHERE authors.name = 'Ken Akamatsu'
+ #
+ # However, this could result in many rows that contain redundant data. After
+ # having received the first row, we already have enough data to instantiate
+ # the Author object. In all subsequent rows, only the data for the joined
+ # 'books' table is useful; the joined 'authors' data is just redundant, and
+ # processing this redundant data takes memory and CPU time. The problem
+ # quickly becomes worse and worse as the level of eager loading increases
+ # (i.e. if ActiveRecord is to eager load the associations' assocations as
+ # well).
+ #
+ # The second strategy is to use multiple database queries, one for each
+ # level of association. Since Rails 2.1, this is the default strategy. In
+ # situations where a table join is necessary (e.g. when the +:conditions+
+ # option references an association's column), it will fallback to the table
+ # join strategy.
+ #
+ # See also ActiveRecord::Associations::ClassMethods, which explains eager
+ # loading in a more high-level (application developer-friendly) manner.
module ClassMethods
-
- # Loads the named associations for the activerecord record (or records) given
- # preload_options is passed only one level deep: don't pass to the child associations when associations is a Hash
protected
+
+ # Eager loads the named associations for the given ActiveRecord record(s).
+ #
+ # In this description, 'association name' shall refer to the name passed
+ # to an association creation method. For example, a model that specifies
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
+ # names +:author+ and +:buyers+.
+ #
+ # == Parameters
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
+ # i.e. +records+ itself may also contain arrays of records. In any case,
+ # +preload_associations+ will preload the associations all records by
+ # flattening +records+.
+ #
+ # +associations+ specifies one or more associations that you want to
+ # preload. It may be:
+ # - a Symbol or a String which specifies a single association name. For
+ # example, specifiying +:books+ allows this method to preload all books
+ # for an Author.
+ # - an Array which specifies multiple association names. This array
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
+ # allows this method to preload an author's avatar as well as all of his
+ # books.
+ # - a Hash which specifies multiple association names, as well as
+ # association names for the to-be-preloaded association objects. For
+ # example, specifying <tt>{ :author => :avatar }</tt> will preload a
+ # book's author, as well as that author's avatar.
+ #
+ # +:associations+ has the same format as the +:include+ option for
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
+ #
+ # :books
+ # [ :books, :author ]
+ # { :author => :avatar }
+ # [ :books, { :author => :avatar } ]
+ #
+ # +preload_options+ contains options that will be passed to ActiveRecord#find
+ # (which is called under the hood for preloading records). But it is passed
+ # only one level deep in the +associations+ argument, i.e. it's not passed
+ # to the child associations when +associations+ is a Hash.
def preload_associations(records, associations, preload_options={})
records = [records].flatten.compact.uniq
return if records.empty?
@@ -30,6 +104,8 @@ module ActiveRecord
private
+ # Preloads a specific named association for the given records. This is
+ # called by +preload_associations+ as its base case.
def preload_one_association(records, association, preload_options={})
class_to_reflection = {}
# Not all records have the same class, so group then preload
@@ -37,6 +113,10 @@ module ActiveRecord
# unnecessarily
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
+
+ # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
+ # the following could call 'preload_belongs_to_association',
+ # 'preload_has_many_association', etc.
send("preload_#{reflection.macro}_association", records, reflection, preload_options)
end
end
@@ -77,6 +157,10 @@ module ActiveRecord
end
end
+ # Given a collection of ActiveRecord objects, constructs a Hash which maps
+ # the objects' IDs to the relevant objects. Returns a 2-tuple
+ # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
+ # and +ids+ is an Array of record IDs.
def construct_id_map(records, primary_key=nil)
id_to_record_map = {}
ids = []
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 3f8ec4d09c..d1a0b2f96a 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -955,8 +955,6 @@ module ActiveRecord
# destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
# is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
# a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
- # When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
- # this results in a counter with +NULL+ value, which will never increment.
# Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
# [:include]
# Specify second-order associations that should be eager loaded when this object is loaded.
@@ -1479,6 +1477,8 @@ module ActiveRecord
end
end
+ # Creates before_destroy callback methods that nullify, delete or destroy
+ # has_one associated objects, according to the defined :dependent rule.
def configure_dependency_for_has_one(reflection)
if reflection.options.include?(:dependent)
case reflection.options[:dependent]
@@ -1492,6 +1492,10 @@ module ActiveRecord
when :delete
method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
define_method(method_name) do
+ # Retrieve the associated object and delete it. The retrieval
+ # is necessary because there may be multiple associated objects
+ # with foreign keys pointing to this object, and we only want
+ # to delete the correct one, not all of them.
association = send(reflection.name)
association.delete unless association.nil?
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 3e8c573d09..6a1a3794a2 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -415,6 +415,31 @@ module ActiveRecord #:nodoc:
@@subclasses = {}
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
cattr_accessor :configurations, :instance_writer => false
@@configurations = {}
@@ -487,7 +512,7 @@ module ActiveRecord #:nodoc:
#
# All approaches accept an options hash as their last parameter.
#
- # ==== Attributes
+ # ==== Parameters
#
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or <tt>[ "user_name = ?", username ]</tt>. See conditions in the intro.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
@@ -585,8 +610,8 @@ module ActiveRecord #:nodoc:
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
- # object with the attributes you specified in the SQL query.
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
@@ -595,7 +620,7 @@ module ActiveRecord #:nodoc:
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
# no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
- # change your call if you switch engines
+ # change your call if you switch engines.
#
# ==== Examples
# # A simple SQL query spanning multiple tables
@@ -672,7 +697,7 @@ module ActiveRecord #:nodoc:
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
- # ==== Attributes
+ # ==== 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.
@@ -700,9 +725,10 @@ module ActiveRecord #:nodoc:
# 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.
#
- # Objects are _not_ instantiated with this method.
+ # Objects are _not_ instantiated with this method, and so +:dependent+ rules
+ # defined on associations are not honered.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +id+ - Can be either an Integer or an Array of Integers.
#
@@ -725,7 +751,7 @@ module ActiveRecord #:nodoc:
# This essentially finds the object (or multiple objects) with the given id, creates a new object
# from the attributes, and then calls destroy on it.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +id+ - Can be either an Integer or an Array of Integers.
#
@@ -749,7 +775,7 @@ module ActiveRecord #:nodoc:
# 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.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
# What goes into the SET clause.
@@ -795,34 +821,39 @@ module ActiveRecord #:nodoc:
# many records. If you want to simply delete records without worrying about dependent associations or
# callbacks, use the much faster +delete_all+ method instead.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +conditions+ - Conditions are specified the same way as with +find+ method.
#
# ==== Example
#
- # Person.destroy_all "last_login < '2004-04-04'"
+ # 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)
def destroy_all(conditions = nil)
find(:all, :conditions => conditions).each { |object| object.destroy }
end
# 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+. Careful with relations
- # though, in particular <tt>:dependent</tt> is not taken into account.
+ # 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.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +conditions+ - Conditions are specified the same way as with +find+ method.
#
# ==== Example
#
- # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
+ # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
+ # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
#
- # This deletes the affected posts all at once with a single DELETE statement. If you need to destroy dependent
+ # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
sql = "DELETE FROM #{quoted_table_name} "
@@ -834,7 +865,7 @@ module ActiveRecord #:nodoc:
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
#
@@ -852,7 +883,7 @@ module ActiveRecord #:nodoc:
# with the given ID, altering the given hash of counters by the amount
# given by the corresponding value:
#
- # ==== Attributes
+ # ==== Parameters
#
# * +id+ - The id of the object you wish to update a counter on.
# * +counters+ - An Array of Hashes containing the names of the fields
@@ -882,7 +913,7 @@ module ActiveRecord #:nodoc:
# For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
# shown it would have to run an SQL query to find how many posts and comments there are.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +counter_name+ - The name of the field that should be incremented.
# * +id+ - The id of the object that should be incremented.
@@ -899,7 +930,7 @@ module ActiveRecord #:nodoc:
#
# This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +counter_name+ - The name of the field that should be decremented.
# * +id+ - The id of the object that should be decremented.
@@ -994,7 +1025,7 @@ module ActiveRecord #:nodoc:
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
# class on retrieval or SerializationTypeMismatch will be raised.
#
- # ==== Attributes
+ # ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
# * +class_name+ - Optional, class name that the object type should be equal to.
@@ -1221,7 +1252,32 @@ module ActiveRecord #:nodoc:
end
end
- # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
+ # Resets all the cached information about columns, which will cause them
+ # to be reloaded on the next request.
+ #
+ # The most common usage pattern for this method is probably in a migration,
+ # when just after creating a table you want to populate it with some default
+ # values, eg:
+ #
+ # class CreateJobLevels < ActiveRecord::Migration
+ # def self.up
+ # create_table :job_levels do |t|
+ # t.integer :id
+ # t.string :name
+ #
+ # t.timestamps
+ # end
+ #
+ # JobLevel.reset_column_information
+ # %w{assistant executive manager director}.each do |type|
+ # JobLevel.create(:name => type)
+ # end
+ # end
+ #
+ # def self.down
+ # drop_table :job_levels
+ # end
+ # end
def reset_column_information
generated_methods.each { |name| undef_method(name) }
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
@@ -1885,6 +1941,9 @@ module ActiveRecord #:nodoc:
# end
# end
# end
+ #
+ # *Note*: the +:find+ scope also has effect on update and deletion methods,
+ # like +update_all+ and +delete_all+.
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
@@ -2232,7 +2291,28 @@ module ActiveRecord #:nodoc:
end
- # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
+ # Returns a String, which Action Pack uses for constructing an URL to this
+ # object. The default implementation returns this record's id as a String,
+ # or nil if this record's unsaved.
+ #
+ # For example, suppose that you have a Users model, and that you have a
+ # <tt>map.resources :users</tt> route. Normally, +users_path+ will
+ # construct an URI with the user object's 'id' in it:
+ #
+ # user = User.find_by_name('Phusion')
+ # user_path(path) # => "/users/1"
+ #
+ # You can override +to_param+ in your model to make +users_path+ construct
+ # an URI using the user's name instead of the user's id:
+ #
+ # class User < ActiveRecord::Base
+ # def to_param # overridden
+ # name
+ # end
+ # end
+ #
+ # user = User.find_by_name('Phusion')
+ # user_path(path) # => "/users/Phusion"
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
@@ -2317,6 +2397,9 @@ module ActiveRecord #:nodoc:
#
# 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.
def delete
self.class.delete(id) unless new_record?
freeze
@@ -2461,10 +2544,25 @@ module ActiveRecord #:nodoc:
end
# Allows you to set all the attributes at once by passing in a hash with keys
- # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
- # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
- # specify which attributes *can* be accessed with the +attr_accessible+ macro. Then all the
+ # matching the attribute names (which again matches the column names).
+ #
+ # If +guard_protected_attributes+ is true (the default), then sensitive
+ # attributes can be protected from this form of mass-assignment by using
+ # the +attr_protected+ macro. Or you can alternatively specify which
+ # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
# attributes not included in that won't be allowed to be mass-assigned.
+ #
+ # class User < ActiveRecord::Base
+ # attr_protected :is_admin
+ # end
+ #
+ # user = User.new
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
+ # user.username # => "Phusion"
+ # user.is_admin? # => false
+ #
+ # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
+ # user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
return if new_attributes.nil?
attributes = new_attributes.dup
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index b7d7384548..432c341e6c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -11,6 +11,21 @@ module ActiveRecord
# Connection pool base class for managing ActiveRecord database
# connections.
#
+ # == Introduction
+ #
+ # A connection pool synchronizes thread access to a limited number of
+ # database connections. The basic idea is that each thread checks out a
+ # database connection from the pool, uses that connection, and checks the
+ # connection back in. ConnectionPool is completely thread-safe, and will
+ # ensure that a connection cannot be used by two threads at the same time,
+ # as long as ConnectionPool's contract is correctly followed. It will also
+ # handle cases in which there are more threads than connections: if all
+ # connections have been checked out, and a thread tries to checkout a
+ # connection anyway, then ConnectionPool will wait until some other thread
+ # has checked in a connection.
+ #
+ # == Obtaining (checking out) a connection
+ #
# Connections can be obtained and used from a connection pool in several
# ways:
#
@@ -28,6 +43,11 @@ module ActiveRecord
# obtains a connection, yields it as the sole argument to the block,
# and returns it to the pool after the block completes.
#
+ # Connections in the pool are actually AbstractAdapter objects (or objects
+ # compatible with AbstractAdapter's interface).
+ #
+ # == Options
+ #
# There are two connection-pooling-related options that you can add to
# your database connection configuration:
#
@@ -37,6 +57,12 @@ module ActiveRecord
class ConnectionPool
attr_reader :spec
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
+ # object which describes database connection information (e.g. adapter,
+ # host name, username, password, etc), as well as the maximum size for
+ # this ConnectionPool.
+ #
+ # The default ConnectionPool maximum size is 5.
def initialize(spec)
@spec = spec
# The cache of reserved connections mapped to threads
@@ -87,7 +113,7 @@ module ActiveRecord
!@connections.empty?
end
- # Disconnect all connections in the pool.
+ # Disconnects all connections in the pool, and clears the pool.
def disconnect!
@reserved_connections.each do |name,conn|
checkin conn
@@ -128,7 +154,22 @@ module ActiveRecord
end
end
- # Check-out a database connection from the pool.
+ # Check-out a database connection from the pool, indicating that you want
+ # to use it. You should call #checkin when you no longer need this.
+ #
+ # This is done by either returning an existing connection, or by creating
+ # a new connection. If the maximum number of connections for this pool has
+ # already been reached, but the pool is empty (i.e. they're all being used),
+ # then this method will wait until a thread has checked in a connection.
+ # The wait time is bounded however: if no connection can be checked out
+ # within the timeout specified for this pool, then a ConnectionTimeoutError
+ # exception will be raised.
+ #
+ # Returns: an AbstractAdapter object.
+ #
+ # Raises:
+ # - ConnectionTimeoutError: no connection can be obtained from the pool
+ # within the timeout period.
def checkout
# Checkout an available connection
@connection_mutex.synchronize do
@@ -153,7 +194,11 @@ module ActiveRecord
end
end
- # Check-in a database connection back into the pool.
+ # Check-in a database connection back into the pool, indicating that you
+ # no longer need this connection.
+ #
+ # +conn+: an AbstractAdapter object, which was obtained by earlier by
+ # calling +checkout+ on this pool.
def checkin(conn)
@connection_mutex.synchronize do
conn.run_callbacks :checkin
@@ -207,6 +252,29 @@ module ActiveRecord
end
end
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
+ # for keeping separate connection pools for ActiveRecord models that connect
+ # to different databases.
+ #
+ # For example, suppose that you have 5 models, with the following hierarchy:
+ #
+ # |
+ # +-- Book
+ # | |
+ # | +-- ScaryBook
+ # | +-- GoodBook
+ # +-- Author
+ # +-- BankAccount
+ #
+ # Suppose that Book is to connect to a separate database (i.e. one other
+ # than the default database). Then Book, ScaryBook and GoodBook will all use
+ # the same connection pool. Likewise, Author and BankAccount will use the
+ # same connection pool. However, the connection pool used by Author/BankAccount
+ # is not the same as the one used by Book/ScaryBook/GoodBook.
+ #
+ # Normally there is only a single ConnectionHandler instance, accessible via
+ # ActiveRecord::Base.connection_handler. ActiveRecord models use this to
+ # determine that connection pool that they should use.
class ConnectionHandler
def initialize(pools = {})
@connection_pools = pools
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 8fc89de22b..10dc1a81f3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -98,8 +98,14 @@ module ActiveRecord
add_limit_offset!(sql, options) if options
end
- # Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
+ # Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
+ # fragment that has the same semantics as LIMIT and OFFSET.
+ #
+ # +options+ must be a Hash which contains a +:limit+ option (required)
+ # and an +:offset+ option (optional).
+ #
# This method *modifies* the +sql+ parameter.
+ #
# ===== Examples
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
# generates
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 bececf82a0..c29c1562b4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -31,19 +31,25 @@ module ActiveRecord
# See the concrete implementation for details on the expected parameter values.
def columns(table_name, name = nil) end
- # Creates a new table
+ # Creates a new table with the name +table_name+. +table_name+ may either
+ # be a String or a Symbol.
+ #
# There are two ways to work with +create_table+. You can use the block
# form or the regular form, like this:
#
# === Block form
- # # create_table() yields a TableDefinition instance
+ # # create_table() passes a TableDefinition object to the block.
+ # # This form will not only create the table, but also columns for the
+ # # table.
# create_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
# # Other fields here
# end
#
# === Regular form
+ # # Creates a table called 'suppliers' with no columns.
# create_table(:suppliers)
+ # # Add a column to 'suppliers'.
# add_column(:suppliers, :name, :string, {:limit => 60})
#
# The +options+ hash can include the following keys:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7c37916367..c5183357a1 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -13,15 +13,19 @@ require 'active_record/connection_adapters/abstract/query_cache'
module ActiveRecord
module ConnectionAdapters # :nodoc:
+ # ActiveRecord supports multiple database systems. AbstractAdapter and
+ # related classes form the abstraction layer which makes this possible.
+ # An AbstractAdapter represents a connection to a database, and provides an
+ # abstract interface for database-specific functionality such as establishing
+ # a connection, escaping values, building the right SQL fragments for ':offset'
+ # and ':limit' options, etc.
+ #
# All the concrete database adapters follow the interface laid down in this class.
- # You can use this interface directly by borrowing the database connection from the Base with
- # Base.connection.
+ # ActiveRecord::Base.connection returns an AbstractAdapter object, which
+ # you can use.
#
- # Most of the methods in the adapter are useful during migrations. Most
- # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
- # SchemaStatements#add_index, SchemaStatements#remove_index,
- # SchemaStatements#add_column, SchemaStatements#change_column and
- # SchemaStatements#remove_column are very useful.
+ # Most of the methods in the adapter are useful during migrations. Most
+ # notably, the instance methods provided by SchemaStatement are very useful.
class AbstractAdapter
include Quoting, DatabaseStatements, SchemaStatements
include QueryCache
@@ -91,26 +95,31 @@ module ActiveRecord
# CONNECTION MANAGEMENT ====================================
- # Is this connection active and ready to perform queries?
+ # Checks whether the connection to the database is still active. This includes
+ # checking whether the database is actually capable of responding, i.e. whether
+ # the connection isn't stale.
def active?
@active != false
end
- # Close this connection and open a new one in its place.
+ # Disconnects from the database if already connected, and establishes a
+ # new connection with the database.
def reconnect!
@active = true
end
- # Close this connection
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
@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.
+ # database-dependent operation.
+ #
+ # The default implementation does nothing; the implementation should be
+ # overridden by concrete adapters.
def reset!
# this should be overridden by concrete adapters
end
@@ -121,15 +130,19 @@ module ActiveRecord
false
end
- # Verify this connection by calling <tt>active?</tt> and reconnecting if
- # the connection is no longer active.
+ # Checks whether the connection to the database is still active (i.e. not stale).
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
+ # is no longer active, then this method will reconnect to the database.
def verify!(*ignored)
reconnect! unless active?
end
- # Provides access to the underlying database connection. Useful for
- # when you need to call a proprietary method such as postgresql's lo_*
- # methods
+ # Provides access to the underlying database driver for this adapter. For
+ # example, this method returns a Mysql object in case of MysqlAdapter,
+ # and a PGconn object in case of PostgreSQLAdapter.
+ #
+ # This is useful for when you need to call a proprietary method such as
+ # PostgreSQL's lo_* methods.
def raw_connection
@connection
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 970da701c7..27b5aca18f 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -1,7 +1,8 @@
require 'thread'
module ActiveRecord
- module Transactions # :nodoc:
+ # See ActiveRecord::Transactions::ClassMethods for documentation.
+ module Transactions
class TransactionError < ActiveRecordError # :nodoc:
end
@@ -15,26 +16,33 @@ module ActiveRecord
end
end
- # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
- # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
- # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
- # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
- # not at all. Example:
+ # Transactions are protective blocks where SQL statements are only permanent
+ # if they can all succeed as one atomic action. The classic example is a
+ # transfer between two accounts where you can only have a deposit if the
+ # withdrawal succeeded and vice versa. Transactions enforce the integrity of
+ # the database and guard the data against program errors or database
+ # break-downs. So basically you should use transaction blocks whenever you
+ # have a number of statements that must be executed together or not at all.
+ # Example:
#
- # transaction do
+ # ActiveRecord::Base.transaction do
# david.withdrawal(100)
# mary.deposit(100)
# end
#
- # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
- # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
- # that the objects will _not_ have their instance data returned to their pre-transactional state.
+ # This example will only take money from David and give to Mary if neither
+ # +withdrawal+ nor +deposit+ raises an exception. Exceptions will force a
+ # ROLLBACK that returns the database to the state before the transaction was
+ # begun. Be aware, though, that the objects will _not_ have their instance
+ # data returned to their pre-transactional state.
#
# == Different Active Record classes in a single transaction
#
# Though the transaction class method is called on some Active Record class,
# the objects within the transaction block need not all be instances of
- # that class.
+ # that class. This is because transactions are per-database connection, not
+ # per-model.
+ #
# In this example a <tt>Balance</tt> record is transactionally saved even
# though <tt>transaction</tt> is called on the <tt>Account</tt> class:
#
@@ -43,6 +51,14 @@ module ActiveRecord
# account.save!
# end
#
+ # Note that the +transaction+ method is also available as a model instance
+ # method. For example, you can also do this:
+ #
+ # balance.transaction do
+ # balance.save!
+ # account.save!
+ # end
+ #
# == Transactions are not distributed across database connections
#
# A transaction acts on a single database connection. If you have
@@ -62,17 +78,48 @@ module ActiveRecord
#
# == Save and destroy are automatically wrapped in a transaction
#
- # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
- # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
- # depends on or you can raise exceptions in the callbacks to rollback, including <tt>after_*</tt> callbacks.
+ # Both Base#save and Base#destroy come wrapped in a transaction that ensures
+ # that whatever you do in validations or callbacks will happen under the
+ # protected cover of a transaction. So you can use validations to check for
+ # values that the transaction depends on or you can raise exceptions in the
+ # callbacks to rollback, including <tt>after_*</tt> callbacks.
#
# == Exception handling and rolling back
#
- # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
- # should be ready to catch those in your application code.
+ # Also have in mind that exceptions thrown within a transaction block will
+ # be propagated (after triggering the ROLLBACK), so you should be ready to
+ # catch those in your application code.
#
- # One exception is the ActiveRecord::Rollback exception, which will trigger a ROLLBACK when raised,
- # but not be re-raised by the transaction block.
+ # One exception is the ActiveRecord::Rollback exception, which will trigger
+ # a ROLLBACK when raised, but not be re-raised by the transaction block.
+ #
+ # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
+ # inside a transaction block. StatementInvalid exceptions indicate that an
+ # error occurred at the database level, for example when a unique constraint
+ # is violated. On some database systems, such as PostgreSQL, database errors
+ # inside a transaction causes the entire transaction to become unusable
+ # until it's restarted from the beginning. Here is an example which
+ # demonstrates the problem:
+ #
+ # # Suppose that we have a Number model with a unique column called 'i'.
+ # Number.transaction do
+ # Number.create(:i => 0)
+ # begin
+ # # This will raise a unique constraint error...
+ # Number.create(:i => 0)
+ # rescue ActiveRecord::StatementInvalid
+ # # ...which we ignore.
+ # end
+ #
+ # # On PostgreSQL, the transaction is now unusable. The following
+ # # statement will cause a PostgreSQL error, even though the unique
+ # # constraint is no longer violated:
+ # Number.create(:i => 1)
+ # # => "PGError: ERROR: current transaction is aborted, commands
+ # # ignored until end of transaction block"
+ # end
+ #
+ # One should restart the entire transaction if a StatementError occurred.
module ClassMethods
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
def transaction(&block)
@@ -86,6 +133,7 @@ module ActiveRecord
end
end
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
def transaction(&block)
self.class.transaction(&block)
end
@@ -122,6 +170,9 @@ module ActiveRecord
# 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.
+ #
+ # This method is available within the context of an ActiveRecord::Base
+ # instance.
def with_transaction_returning_status(method, *args)
status = nil
transaction do
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index ed366527ce..9220eae4d1 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -625,10 +625,6 @@ module ActiveRecord
# When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
# attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
#
- # Because this check is performed outside the database there is still a chance that duplicate values
- # will be inserted in two parallel transactions. To guarantee against this you should create a
- # unique index on the field. See +add_index+ for more information.
- #
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
@@ -641,6 +637,70 @@ module ActiveRecord
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ #
+ # === Concurrency and integrity
+ #
+ # Using this validation method in conjunction with ActiveRecord::Base#save
+ # does not guarantee the absence of duplicate record insertions, because
+ # uniqueness checks on the application level are inherently prone to race
+ # conditions. For example, suppose that two users try to post a Comment at
+ # the same time, and a Comment's title must be unique. At the database-level,
+ # the actions performed by these users could be interleaved in the following manner:
+ #
+ # User 1 | User 2
+ # ------------------------------------+--------------------------------------
+ # # User 1 checks whether there's |
+ # # already a comment with the title |
+ # # 'My Post'. This is not the case. |
+ # SELECT * FROM comments |
+ # WHERE title = 'My Post' |
+ # |
+ # | # User 2 does the same thing and also
+ # | # infers that his title is unique.
+ # | SELECT * FROM comments
+ # | WHERE title = 'My Post'
+ # |
+ # # User 1 inserts his comment. |
+ # INSERT INTO comments |
+ # (title, content) VALUES |
+ # ('My Post', 'hi!') |
+ # |
+ # | # User 2 does the same thing.
+ # | INSERT INTO comments
+ # | (title, content) VALUES
+ # | ('My Post', 'hello!')
+ # |
+ # | # ^^^^^^
+ # | # Boom! We now have a duplicate
+ # | # title!
+ #
+ # This could even happen if you use transactions with the 'serializable'
+ # isolation level. There are several ways to get around this problem:
+ # - By locking the database table before validating, and unlocking it after
+ # saving. However, table locking is very expensive, and thus not
+ # recommended.
+ # - By locking a lock file before validating, and unlocking it after saving.
+ # This does not work if you've scaled your Rails application across
+ # multiple web servers (because they cannot share lock files, or cannot
+ # do that efficiently), and thus not recommended.
+ # - Creating a unique index on the field, by using
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
+ # rare case that a race condition occurs, the database will guarantee
+ # the field's uniqueness.
+ #
+ # When the database catches such a duplicate insertion,
+ # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
+ # exception. You can either choose to let this error propagate (which
+ # will result in the default Rails exception page being shown), or you
+ # can catch it and restart the transaction (e.g. by telling the user
+ # that the title already exists, and asking him to re-enter the title).
+ # This technique is also known as optimistic concurrency control:
+ # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
+ #
+ # Active Record currently provides no way to distinguish unique
+ # index constraint errors from other types of database errors, so you
+ # will have to parse the (database-specific) exception message to detect
+ # such a case.
def validates_uniqueness_of(*attr_names)
configuration = { :case_sensitive => true }
configuration.update(attr_names.extract_options!)