Permalink
Browse files

Merge branch 'master' of git@github.com:lifo/docrails

  • Loading branch information...
2 parents 6664177 + 91871f2 commit ea1eb57f56a4143d575ad8a3df54e063d8cea364 @FotoVerite FotoVerite committed Sep 22, 2008
Showing with 442 additions and 89 deletions.
  1. +87 −3 activerecord/lib/active_record/association_preload.rb
  2. +6 −0 activerecord/lib/active_record/associations.rb
  3. +70 −22 activerecord/lib/active_record/base.rb
  4. +7 −1 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
  5. +8 −2 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  6. +64 −4 activerecord/lib/active_record/validations.rb
  7. +13 −2 activesupport/lib/active_support/base64.rb
  8. +9 −9 activesupport/lib/active_support/core_ext/array/access.rb
  9. +3 −0 activesupport/lib/active_support/core_ext/base64/encoding.rb
  10. +36 −16 activesupport/lib/active_support/core_ext/object/misc.rb
  11. BIN railties/doc/guides/bechmarking and profiling/.DS_Store
  12. BIN railties/doc/guides/bechmarking and profiling/Examples/.DS_Store
  13. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/Basics.html
  14. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/Examples/graph.html
  15. BIN railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/Images/KGraph.png
  16. BIN railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/Images/KList.png
  17. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/appendix.txt
  18. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/basics.txt
  19. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/definitions.txt
  20. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/digging_deeper.txt
  21. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/edge rails features.txt
  22. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/gameplan.txt
  23. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/preamble.html
  24. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/preamble.txt
  25. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/rubyprof.txt
  26. 0 railties/doc/guides/{bechmarking and profiling → benchmarking_and_profiling}/statistics.txt
  27. +36 −1 railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt
  28. +43 −20 railties/doc/guides/index.txt
  29. +59 −9 railties/doc/guides/routing/routing_outside_in.txt
  30. +1 −0 railties/lib/tasks/documentation.rake
@@ -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,13 +104,19 @@ def preload_associations(records, associations, preload_options={})
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
# group on the reflection itself so that if various subclass share the same association then we do not split them
# 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 @@ def set_association_single_records(id_to_record_map, reflection_name, associated
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)
id_to_record_map = {}
ids = []
@@ -1452,6 +1452,8 @@ def configure_dependency_for_has_many(reflection)
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]
@@ -1465,6 +1467,10 @@ def configure_dependency_for_has_one(reflection)
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.class.delete(association.id) unless association.nil?
end
@@ -512,7 +512,7 @@ class << self # Class methods
#
# 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".
@@ -697,7 +697,7 @@ def create(attributes = nil, &block)
# 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.
@@ -725,9 +725,10 @@ def update(id, attributes)
# 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.
#
@@ -750,7 +751,7 @@ def delete(id)
# 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.
#
@@ -774,7 +775,7 @@ def destroy(id)
# 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.
@@ -820,34 +821,39 @@ def update_all(updates, conditions = nil, options = {})
# 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} "
@@ -859,7 +865,7 @@ def delete_all(conditions = nil)
# 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.
#
@@ -877,7 +883,7 @@ def count_by_sql(sql)
# 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
@@ -907,7 +913,7 @@ def update_counters(id, counters)
# 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.
@@ -924,7 +930,7 @@ def increment_counter(counter_name, id)
#
# 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.
@@ -1019,7 +1025,7 @@ def readonly_attributes
# 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.
@@ -1927,6 +1933,9 @@ def define_attr_method(name, value=nil, &block)
# 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)
@@ -2274,7 +2283,28 @@ def id
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
@@ -2356,6 +2386,9 @@ def save!
# Deletes the record in the database and freezes this instance to reflect that no changes should
# be made (since they can't be persisted).
+ #
+ # 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 destroy
unless new_record?
connection.delete <<-end_sql, "#{self.class.name} Destroy"
@@ -2493,10 +2526,25 @@ def []=(attr_name, value)
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
@@ -98,8 +98,14 @@ def add_limit!(sql, options)
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
Oops, something went wrong.

0 comments on commit ea1eb57

Please sign in to comment.