diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-10-05 22:16:26 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-10-05 22:16:26 +0100 |
commit | a2932784bb71e72a78c32819ebd7ed2bed551e3e (patch) | |
tree | 99bfd589a48153e33f19ae72baa6e98f5708a9b8 /activerecord | |
parent | 4df45d86097efbeabceecfe53d8ea2da9ccbb107 (diff) | |
download | rails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.tar.gz rails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.tar.bz2 rails-a2932784bb71e72a78c32819ebd7ed2bed551e3e.zip |
Merge docrails
Diffstat (limited to 'activerecord')
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!) |