aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG15
-rwxr-xr-xactiverecord/Rakefile2
-rw-r--r--activerecord/lib/active_record/aggregations.rb18
-rw-r--r--activerecord/lib/active_record/association_preload.rb2
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/associations.rb142
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb35
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb5
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/associations/belongs_to_association.rb5
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb4
-rwxr-xr-x[-rw-r--r--]activerecord/lib/active_record/associations/has_one_association.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb8
-rwxr-xr-xactiverecord/lib/active_record/base.rb238
-rw-r--r--activerecord/lib/active_record/calculations.rb22
-rwxr-xr-xactiverecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb6
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb14
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/mysql_adapter.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/dirty.rb46
-rwxr-xr-xactiverecord/lib/active_record/fixtures.rb49
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb14
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/named_scope.rb36
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/schema.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rwxr-xr-xactiverecord/lib/active_record/validations.rb52
-rw-r--r--activerecord/lib/active_record/version.rb4
-rw-r--r--activerecord/test/cases/active_schema_test_mysql.rb73
-rw-r--r--activerecord/test/cases/adapter_test.rb20
-rwxr-xr-x[-rw-r--r--]activerecord/test/cases/associations/belongs_to_associations_test.rb38
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb72
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb1
-rwxr-xr-x[-rw-r--r--]activerecord/test/cases/associations/has_one_associations_test.rb5
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rwxr-xr-xactiverecord/test/cases/associations_test.rb16
-rw-r--r--activerecord/test/cases/datatype_test_postgresql.rb8
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb18
-rw-r--r--activerecord/test/cases/finder_test.rb2
-rwxr-xr-xactiverecord/test/cases/inheritance_test.rb18
-rw-r--r--activerecord/test/cases/method_scoping_test.rb16
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/named_scope_test.rb40
-rw-r--r--activerecord/test/cases/reflection_test.rb5
-rw-r--r--activerecord/test/cases/transactions_test.rb26
-rwxr-xr-xactiverecord/test/cases/validations_test.rb6
-rwxr-xr-xactiverecord/test/models/company.rb2
57 files changed, 802 insertions, 395 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index cd526a52a7..a65771648e 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,4 +1,17 @@
-*2.1.0 RC1 (May 11th, 2008)*
+*Edge*
+
+* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
+
+
+*2.1.0 (May 31st, 2008)*
+
+* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]
+
+* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
+
+* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
+
+* Added first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 043ab6d551..fc068b16c3 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -171,7 +171,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 61446cde36..a5d3a50ef1 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -92,19 +92,19 @@ module ActiveRecord
#
# == Writing value objects
#
- # Value objects are immutable and interchangeable objects that represent a given value, such as a +Money+ object representing
- # $5. Two +Money+ objects both representing $5 should be equal (through methods such as == and <=> from +Comparable+ if ranking
- # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as +Customer+ can
+ # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
+ # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
+ # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
# easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
- # relational unique identifiers (such as primary keys). Normal <tt>ActiveRecord::Base</tt> classes are entity objects.
+ # 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 <tt>Money#exchanged_to</tt> method that
+ # 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
# 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.
#
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
- # change it afterwards will result in a <tt>ActiveSupport::FrozenObjectError</tt>.
+ # change it afterwards will result in a ActiveSupport::FrozenObjectError.
#
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
# immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
@@ -123,8 +123,8 @@ module ActiveRecord
#
# Options are:
# * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
- # from the part id. So <tt>composed_of :address</tt> will by default be linked to the +Address+ class, but
- # if the real class name is +CompanyAddress+, you'll have to specify it with this option.
+ # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
+ # if the real class name is CompanyAddress, you'll have to specify it with this option.
# * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
# to a constructor parameter on the value class.
# * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index a3d1f12b03..087ed2a587 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -34,7 +34,7 @@ module ActiveRecord
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
- # unncessarily
+ # 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
send("preload_#{reflection.macro}_association", records, reflection, preload_options)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index c17e35f5e0..a3d1bbbada 100644..100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -155,7 +155,7 @@ module ActiveRecord
#
# == Cardinality and associations
#
- # ActiveRecord associations can be used to describe one-to-one, one-to-many and many-to-many
+ # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
# relationships between models. Each model uses an association to describe its role in
# the relation. The +belongs_to+ association is always used in the model that has
# the foreign key.
@@ -441,9 +441,9 @@ module ActiveRecord
#
# == Eager loading of associations
#
- # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
+ # Eager loading is a way to find objects of a certain class and a number of named associations. This is
# one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
- # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
#
# class Post < ActiveRecord::Base
# belongs_to :author
@@ -452,7 +452,7 @@ module ActiveRecord
#
# Consider the following loop using the class above:
#
- # for post in Post.find(:all)
+ # for post in Post.all
# puts "Post: " + post.title
# puts "Written by: " + post.author.name
# puts "Last comment on: " + post.comments.first.created_on
@@ -462,14 +462,15 @@ module ActiveRecord
#
# for post in Post.find(:all, :include => :author)
#
- # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol, so the find will now weave in a join something
- # like this: <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Doing so will cut down the number of queries from 201 to 101.
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol. After loading the posts, find
+ # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
#
# We can improve upon the situation further by referencing both associations in the finder with:
#
# for post in Post.find(:all, :include => [ :author, :comments ])
#
- # That'll add another join along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt>. And we'll be down to 1 query.
+ # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
+ # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below).
#
# To include a deep hierarchy of associations, use a hash:
#
@@ -482,81 +483,91 @@ module ActiveRecord
# the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
# catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
#
- # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
- # <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
- # <tt>:select</tt> option is ignored.
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
+ # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
+ #
+ # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
- # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
- # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
- # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
+ # will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
+ # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
+ # and not just to the association. You must disambiguate column references for this fallback to happen, for example
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
+ #
+ # If you do want eagerload only some members of an association it is usually more natural to <tt>:include</tt> an association
+ # which has conditions defined on it:
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
+ # end
+ #
+ # Post.find(:all, :include => :approved_comments)
+ #
+ # will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
#
# When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
# before the actual model exists.
#
- # Eager loading is not supported with polymorphic associations up to (and including)
- # version 2.0.2. Given
+ # Eager loading is supported with polymorphic associations.
#
# class Address < ActiveRecord::Base
# belongs_to :addressable, :polymorphic => true
# end
#
- # a call that tries to eager load the addressable model
+ # A call that tries to eager load the addressable model
#
- # Address.find(:all, :include => :addressable) # INVALID
+ # Address.find(:all, :include => :addressable)
#
- # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type
- # is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that early query.
- #
- # In versions greater than 2.0.2 eager loading in polymorphic associations is supported
- # thanks to a change in the overall preloading strategy.
- #
- # It does work the other way around though: if the <tt>User</tt> model is <tt>addressable</tt> you can eager load
- # their addresses with <tt>:include</tt> just fine, every piece needed to construct the query is known beforehand.
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
+ # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
+ # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
+ # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
#
- # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
+ # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
# the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
# for any more successive uses of the table name.
#
- # Post.find :all, :include => :comments
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
- # Post.find :all, :include => :special_comments # STI
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
- # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
+ # Post.find :all, :joins => :comments
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
+ # Post.find :all, :joins => :special_comments # STI
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
+ # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
#
# Acts as tree example:
#
- # TreeMixin.find :all, :include => :children
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # LEFT OUTER JOIN parents_mixins ...
- # TreeMixin.find :all, :include => {:children => {:parent => :children}}
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
- # LEFT OUTER JOIN parents_mixins ...
- # LEFT OUTER JOIN mixins childrens_mixins_2
+ # TreeMixin.find :all, :joins => :children
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # TreeMixin.find :all, :joins => {:children => :parent}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # TreeMixin.find :all, :joins => {:children => {:parent => :children}}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # INNER JOIN mixins childrens_mixins_2
#
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
#
- # Post.find :all, :include => :categories
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # Post.find :all, :include => {:categories => :posts}
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
- # Post.find :all, :include => {:categories => {:posts => :categories}}
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
- # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
+ # Post.find :all, :joins => :categories
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # Post.find :all, :joins => {:categories => :posts}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # Post.find :all, :joins => {:categories => {:posts => :categories}}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
#
# If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
#
- # Post.find :all, :include => :comments, :joins => "inner join comments ..."
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
- # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
- # LEFT OUTER JOIN comments special_comments_posts ...
+ # Post.find :all, :joins => :comments, :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
+ # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
+ # INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
#
# Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
@@ -667,7 +678,7 @@ module ActiveRecord
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
- # but not include the joined columns.
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
@@ -747,12 +758,16 @@ module ActiveRecord
# as the default <tt>:foreign_key</tt>.
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
# <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
# * <tt>:source</tt> - Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
+ # association is a polymorphic +belongs_to+.
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
#
# Option examples:
@@ -819,8 +834,8 @@ module ActiveRecord
# if the real class name is Person, you'll have to specify it with this option.
# * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>authorized = 1</tt>.
- # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
- # such as <tt>last_name, first_name DESC</tt>.
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
# "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
@@ -838,7 +853,6 @@ module ActiveRecord
# 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+.
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
- # Not allowed if the association is polymorphic.
# * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
@@ -938,7 +952,7 @@ module ActiveRecord
#
# Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
# +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
- # +ReadOnly+ (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
+ # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
# associations with attributes to a real join model (see introduction).
#
# Adds the following methods for retrieval and query:
@@ -1009,7 +1023,7 @@ module ActiveRecord
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns.
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
#
# Option examples:
@@ -1339,7 +1353,7 @@ module ActiveRecord
def create_has_one_reflection(association_id, options)
options.assert_valid_keys(
- :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
)
create_reflection(:has_one, association_id, options, self)
@@ -1347,14 +1361,14 @@ module ActiveRecord
def create_has_one_through_reflection(association_id, options)
options.assert_valid_keys(
- :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
)
create_reflection(:has_one, association_id, options, self)
end
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(
- :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
:counter_cache, :extend, :polymorphic, :readonly
)
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 7e47bf7bdf..52d2a9864e 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -48,6 +48,26 @@ module ActiveRecord
end
end
+ # Fetches the first one using SQL if possible.
+ def first(*args)
+ if fetch_first_or_last_using_find? args
+ find(:first, *args)
+ else
+ load_target unless loaded?
+ @target.first(*args)
+ end
+ end
+
+ # Fetches the last one using SQL if possible.
+ def last(*args)
+ if fetch_first_or_last_using_find? args
+ find(:last, *args)
+ else
+ load_target unless loaded?
+ @target.last(*args)
+ end
+ end
+
def to_ary
load_target
@target.to_ary
@@ -146,12 +166,18 @@ module ActiveRecord
if attrs.is_a?(Array)
attrs.collect { |attr| create(attr) }
else
- create_record(attrs) { |record| record.save }
+ create_record(attrs) do |record|
+ yield(record) if block_given?
+ record.save
+ end
end
end
def create!(attrs = {})
- create_record(attrs) { |record| record.save! }
+ create_record(attrs) do |record|
+ yield(record) if block_given?
+ record.save!
+ end
end
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
@@ -330,7 +356,10 @@ module ActiveRecord
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
end
-
+
+ def fetch_first_or_last_using_find?(args)
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 68503a3c40..11c64243a2 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -49,7 +49,7 @@ module ActiveRecord
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
@@ -210,7 +210,8 @@ module ActiveRecord
def raise_on_type_mismatch(record)
unless record.is_a?(@reflection.klass)
- raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.klass} expected, got #{record.class}"
+ message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ raise ActiveRecord::AssociationTypeMismatch, message
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 9ff3f13592..7c28cbdd07 100644..100755
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -42,10 +42,11 @@ module ActiveRecord
private
def find_target
@reflection.klass.find(
- @owner[@reflection.primary_key_name],
+ @owner[@reflection.primary_key_name],
+ :select => @reflection.options[:select],
:conditions => conditions,
:include => @reflection.options[:include],
- :readonly => @reflection.options[:readonly]
+ :readonly => @reflection.options[:readonly]
)
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 9549b959fc..d8146daa54 100644..100755
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -7,10 +7,8 @@ module ActiveRecord
else
@target = (AssociationProxy === record ? record.target : record)
- unless record.new_record?
- @owner[@reflection.primary_key_name] = record.id
- @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
- end
+ @owner[@reflection.primary_key_name] = record.id
+ @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
@updated = true
end
@@ -29,12 +27,13 @@ module ActiveRecord
if @reflection.options[:conditions]
association_class.find(
- @owner[@reflection.primary_key_name],
+ @owner[@reflection.primary_key_name],
+ :select => @reflection.options[:select],
:conditions => conditions,
:include => @reflection.options[:include]
)
else
- association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
+ association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 963a938485..f584a97cbb 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -9,7 +9,7 @@ module ActiveRecord
@reflection.klass.count_by_sql(@finder_sql)
else
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- options[:conditions] = options[:conditions].nil? ?
+ options[:conditions] = options[:conditions].blank? ?
@finder_sql :
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
options[:include] ||= @reflection.options[:include]
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index f683669615..52ced36d16 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -34,7 +34,7 @@ module ActiveRecord
def count(*args)
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
if @reflection.options[:uniq]
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid sql statement.
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
options.merge!(:distinct => true)
end
@@ -237,7 +237,7 @@ module ActiveRecord
end
def build_sti_condition
- "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}"
+ "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
end
alias_method :sql_conditions, :conditions
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 3ff9fe3b9f..c2b3503e0d 100644..100755
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -51,10 +51,11 @@ module ActiveRecord
private
def find_target
@reflection.klass.find(:first,
- :conditions => @finder_sql,
+ :conditions => @finder_sql,
+ :select => @reflection.options[:select],
:order => @reflection.options[:order],
:include => @reflection.options[:include],
- :readonly => @reflection.options[:readonly]
+ :readonly => @reflection.options[:readonly]
)
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index c2604894a8..fab16a4446 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -94,7 +94,7 @@ module ActiveRecord
end
# Checks whether the method is defined in the model or any of its subclasses
- # that also derive from ActiveRecord. Raises DangerousAttributeError if the
+ # that also derive from Active Record. Raises DangerousAttributeError if the
# method is defined by Active Record though.
def instance_method_already_implemented?(method_name)
method_name = method_name.to_s
@@ -162,6 +162,8 @@ module ActiveRecord
evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
end
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
def define_read_method_for_time_zone_conversion(attr_name)
method_body = <<-EOV
def #{attr_name}(reload = false)
@@ -174,7 +176,7 @@ module ActiveRecord
evaluate_attribute_method attr_name, method_body
end
- # Define an attribute ? method.
+ # Defines a predicate method <tt>attr_name?</tt>.
def define_question_method(attr_name)
evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
end
@@ -183,6 +185,8 @@ module ActiveRecord
evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
end
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
def define_write_method_for_time_zone_conversion(attr_name)
method_body = <<-EOV
def #{attr_name}=(time)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9b6183fc02..cfc6a5693a 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2,11 +2,11 @@ require 'yaml'
require 'set'
module ActiveRecord #:nodoc:
- # Generic ActiveRecord exception class.
+ # Generic Active Record exception class.
class ActiveRecordError < StandardError
end
- # Raised when the single-table inheritance mechanism failes to locate the subclass
+ # Raised when the single-table inheritance mechanism fails to locate the subclass
# (for example due to improper usage of column that +inheritance_column+ points to).
class SubclassNotFound < ActiveRecordError #:nodoc:
end
@@ -30,19 +30,19 @@ module ActiveRecord #:nodoc:
class SerializationTypeMismatch < ActiveRecordError
end
- # Raised when adapter not specified on connection (or configuration file config/database.yml misses adapter field).
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
class AdapterNotSpecified < ActiveRecordError
end
- # Raised when ActiveRecord cannot find database adapter specified in config/database.yml or programmatically.
+ # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
class AdapterNotFound < ActiveRecordError
end
- # Raised when connection to the database could not been established (for example when connection= is given a nil object).
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
class ConnectionNotEstablished < ActiveRecordError
end
- # Raised when ActiveRecord cannot find record by given id or set of ids.
+ # Raised when Active Record cannot find record by given id or set of ids.
class RecordNotFound < ActiveRecordError
end
@@ -70,7 +70,7 @@ module ActiveRecord #:nodoc:
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
#
- # Read more about optimistic locking in +ActiveRecord::Locking+ module RDoc.
+ # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
class StaleObjectError < ActiveRecordError
end
@@ -83,12 +83,12 @@ module ActiveRecord #:nodoc:
class ReadOnlyRecord < ActiveRecordError
end
- # Used by ActiveRecord transaction mechanism to distinguish rollback from other exceptional situations.
+ # Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
# You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
class Rollback < ActiveRecordError
end
- # Raised when attribute has a name reserved by ActiveRecord (when attribute has name of one of ActiveRecord instance methods).
+ # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
class DangerousAttributeError < ActiveRecordError
end
@@ -97,7 +97,7 @@ module ActiveRecord #:nodoc:
class MissingAttributeError < NoMethodError
end
- # Raised when an error occured while doing a mass assignment to an attribute through the
+ # Raised when an error occurred while doing a mass assignment to an attribute through the
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
@@ -200,7 +200,7 @@ module ActiveRecord #:nodoc:
#
# All column values are automatically available through basic accessors on the Active Record object, but sometimes you
# want to specialize this behavior. This can be done by overwriting the default accessors (using the same
- # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
+ # name as the attribute) and calling <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually change things.
# Example:
#
# class Song < ActiveRecord::Base
@@ -215,8 +215,8 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
- # read_attribute(:attribute) as a shorter form.
+ # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt> instead of <tt>write_attribute(:attribute, value)</tt> and
+ # <tt>read_attribute(:attribute)</tt> as a shorter form.
#
# == Attribute query methods
#
@@ -236,7 +236,7 @@ module ActiveRecord #:nodoc:
#
# Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
# That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
- # has a balance attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
+ # has a <tt>balance</tt> attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
#
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
# the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
@@ -245,8 +245,8 @@ module ActiveRecord #:nodoc:
# == Dynamic attribute-based finders
#
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
- # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
- # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
+ # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
+ # <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
# <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
# And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
#
@@ -255,8 +255,8 @@ module ActiveRecord #:nodoc:
# <tt>Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
#
- # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
- # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
+ # It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
+ # is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
# actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
#
# The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
@@ -271,7 +271,7 @@ module ActiveRecord #:nodoc:
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
#
# # No 'Winter' tag exists
# winter = Tag.find_or_initialize_by_name("Winter")
@@ -316,8 +316,8 @@ module ActiveRecord #:nodoc:
# class Client < Company; end
# class PriorityClient < Client; end
#
- # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
- # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
+ # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in the companies table with type = "Firm". You can then
+ # fetch this row again using <tt>Company.find(:first, "name = '37signals'")</tt> and it will return a Firm object.
#
# If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
# like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
@@ -329,8 +329,8 @@ module ActiveRecord #:nodoc:
#
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
# All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
- # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
- # and Course *and all its subclasses* will use this connection instead.
+ # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
+ # and Course and all of its subclasses will use this connection instead.
#
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
# requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
@@ -407,7 +407,7 @@ module ActiveRecord #:nodoc:
@@table_name_suffix = ""
# Indicates whether table names should be the pluralized versions of the corresponding class names.
- # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
cattr_accessor :pluralize_table_names, :instance_writer => false
@@pluralize_table_names = true
@@ -446,36 +446,44 @@ module ActiveRecord #:nodoc:
class << self # Class methods
# Find operates with four different retrieval approaches:
#
- # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
+ # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
# If no record can be found for all of the listed ids, then RecordNotFound will be raised.
- # * Find first: This will return the first record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, nil is returned.
- # * Find last: This will return the last record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, nil is returned.
- # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
- #
- # All approaches accept an options hash as their last parameter. The options are:
- #
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
- # or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
+ # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
+ # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
+ # * Find all - This will return all the records matched by the options used.
+ # If no records are found, an empty array is returned. Use
+ # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
+ #
+ # All approaches accept an options hash as their last parameter.
+ #
+ # ==== Attributes
+ #
+ # * <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".
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
+ # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
+ # or named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s).
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
+ # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
# include the joined columns.
- # * <tt>:from</tt>: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
# of a database view).
- # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
- # * <tt>:lock</tt>: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
+ # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
+ # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
# <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
#
- # Examples for find by id:
+ # ==== Examples
+ #
+ # # find by id
# Person.find(1) # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
@@ -486,17 +494,19 @@ module ActiveRecord #:nodoc:
# provide since database rows are unordered. Give an explicit <tt>:order</tt>
# to ensure the results are sorted.
#
- # Examples for find first:
+ # ==== Examples
+ #
+ # # find first
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
# Person.find(:first, :order => "created_on DESC", :offset => 5)
#
- # Examples for find last:
+ # # find last
# Person.find(:last) # returns the last object fetched by SELECT * FROM people
# Person.find(:last, :conditions => [ "user_name = ?", user_name])
# Person.find(:last, :order => "created_on DESC", :offset => 5)
#
- # Examples for find all:
+ # # find all
# Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
# Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
# Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
@@ -504,11 +514,12 @@ module ActiveRecord #:nodoc:
# Person.find(:all, :include => [ :account, :friends ])
# Person.find(:all, :group => "category")
#
- # Example for find with a lock. Imagine two concurrent transactions:
- # each will read person.visits == 2, add 1 to it, and save, resulting
- # in two saves of person.visits = 3. By locking the row, the second
+ # Example for find with a lock: Imagine two concurrent transactions:
+ # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
# transaction has to wait until the first is finished; we get the
- # expected person.visits == 4.
+ # expected <tt>person.visits == 4</tt>.
+ #
# Person.transaction do
# person = Person.find(1, :lock => true)
# person.visits += 1
@@ -527,14 +538,14 @@ module ActiveRecord #:nodoc:
end
end
- # This is an alias for find(:first). You can pass in all the same arguments to this method as you can
- # to find(:first)
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
+ # same arguments to this method as you can to <tt>find(:first)</tt>.
def first(*args)
find(:first, *args)
end
- # This is an alias for find(:last). You can pass in all the same arguments to this method as you can
- # to find(:last)
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
+ # same arguments to this method as you can to <tt>find(:last)</tt>.
def last(*args)
find(:last, *args)
end
@@ -545,8 +556,7 @@ module ActiveRecord #:nodoc:
find(:all, *args)
end
- #
- # Executes a custom sql query against your database and returns all the results. The results will
+ # 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.
@@ -555,13 +565,13 @@ module ActiveRecord #:nodoc:
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
- # The +sql+ parameter is a full sql query as a string. It will be called as is, there will be
+ # 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
#
# ==== Examples
- # # A simple sql query spanning multiple tables
+ # # A simple SQL query spanning multiple tables
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
# > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
@@ -859,9 +869,15 @@ module ActiveRecord #:nodoc:
end
- # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
- # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
- # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
+ # Attributes named in this macro are protected from mass-assignment,
+ # such as <tt>new(attributes)</tt>,
+ # <tt>update_attributes(attributes)</tt>, or
+ # <tt>attributes=(attributes)</tt>.
+ #
+ # Mass-assignment to these attributes will simply be ignored, to assign
+ # to them you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms.
#
# class Customer < ActiveRecord::Base
# attr_protected :credit_rating
@@ -875,7 +891,8 @@ module ActiveRecord #:nodoc:
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # To start from an all-closed default and enable attributes as needed, have a look at attr_accessible.
+ # To start from an all-closed default and enable attributes as needed,
+ # have a look at +attr_accessible+.
def attr_protected(*attributes)
write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
end
@@ -885,19 +902,18 @@ module ActiveRecord #:nodoc:
read_inheritable_attribute("attr_protected")
end
- # Similar to the attr_protected macro, this protects attributes of your model from mass-assignment,
- # such as <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>
- # however, it does it in the opposite way. This locks all attributes and only allows access to the
- # attributes specified. Assignment to attributes not in this list will be ignored and need to be set
- # using the direct writer methods instead. This is meant to protect sensitive attributes from being
- # overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
- # attributes as needed, have a look at attr_protected.
- #
- # ==== Attributes
- #
- # * <tt>*attributes</tt> A comma separated list of symbols that represent columns _not_ to be protected
+ # Specifies a white list of model attributes that can be set via
+ # mass-assignment, such as <tt>new(attributes)</tt>,
+ # <tt>update_attributes(attributes)</tt>, or
+ # <tt>attributes=(attributes)</tt>
#
- # ==== Examples
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
+ # will only set attributes in this list, to assign to the rest of
+ # attributes you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms. If you'd rather start from an all-open
+ # default and restrict attributes as needed, have a look at
+ # +attr_protected+.
#
# class Customer < ActiveRecord::Base
# attr_accessible :name, :nickname
@@ -932,7 +948,7 @@ module ActiveRecord #:nodoc:
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
# then specify the name of that attribute using this method and it will be handled automatically.
# 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.
+ # class on retrieval or SerializationTypeMismatch will be raised.
#
# ==== Attributes
#
@@ -955,12 +971,14 @@ module ActiveRecord #:nodoc:
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
- # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
+ # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
# to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
# in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
#
# Nested classes are given table names prefixed by the singular form of
- # the parent's table name. Enclosing modules are not considered. Examples:
+ # the parent's table name. Enclosing modules are not considered.
+ #
+ # ==== Examples
#
# class Invoice < ActiveRecord::Base; end;
# file class table_name
@@ -974,8 +992,8 @@ module ActiveRecord #:nodoc:
# file class table_name
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
- # Additionally, the class-level table_name_prefix is prepended and the
- # table_name_suffix is appended. So if you have "myapp_" as a prefix,
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
@@ -1054,8 +1072,6 @@ module ActiveRecord #:nodoc:
# Sets the table name to use to the given value, or (if the value
# is nil or false) to the value returned by the given block.
#
- # Example:
- #
# class Project < ActiveRecord::Base
# set_table_name "project"
# end
@@ -1068,8 +1084,6 @@ module ActiveRecord #:nodoc:
# or (if the value is nil or false) to the value returned by the given
# block.
#
- # Example:
- #
# class Project < ActiveRecord::Base
# set_primary_key "sysid"
# end
@@ -1082,8 +1096,6 @@ module ActiveRecord #:nodoc:
# or (if the value # is nil or false) to the value returned by the
# given block.
#
- # Example:
- #
# class Project < ActiveRecord::Base
# set_inheritance_column do
# original_inheritance_column + "_id"
@@ -1105,8 +1117,6 @@ module ActiveRecord #:nodoc:
# If a sequence name is not explicitly set when using PostgreSQL, it
# will discover the sequence corresponding to your primary key for you.
#
- # Example:
- #
# class Project < ActiveRecord::Base
# set_sequence_name "projectseq" # default would have been "project_seq"
# end
@@ -1266,7 +1276,7 @@ module ActiveRecord #:nodoc:
class_of_active_record_descendant(self)
end
- # Set this to true if this is an abstract class (see #abstract_class?).
+ # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
attr_accessor :abstract_class
# Returns whether this class is a base AR class. If A is a base class and
@@ -1282,6 +1292,10 @@ module ActiveRecord #:nodoc:
super
end
+ def sti_name
+ store_full_sti_class ? name : name.demodulize
+ end
+
private
def find_initial(options)
options.update(:limit => 1)
@@ -1441,12 +1455,16 @@ module ActiveRecord #:nodoc:
# Nest the type name in the same module as this class.
# Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
def type_name_with_module(type_name)
- (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
+ if store_full_sti_class
+ type_name
+ else
+ (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
+ end
end
def construct_finder_sql(options)
scope = scope(:find)
- sql = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && quoted_table_name + '.*') || '*'} "
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options, scope)
@@ -1560,8 +1578,8 @@ module ActiveRecord #:nodoc:
def type_condition
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
- type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? name : name.demodulize}' ") do |condition, subclass|
- condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? subclass.name : subclass.name.demodulize}' "
+ type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
+ condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
end
" (#{type_condition}) "
@@ -1713,8 +1731,8 @@ module ActiveRecord #:nodoc:
end
- # Defines an "attribute" method (like #inheritance_column or
- # #table_name). A new (class) method will be created with the
+ # Defines an "attribute" method (like +inheritance_column+ or
+ # +table_name+). A new (class) method will be created with the
# given name. If a value is specified, the new method will
# return that value (as a string). Otherwise, the given block
# will be used to compute the value of the method.
@@ -1891,7 +1909,7 @@ module ActiveRecord #:nodoc:
end
end
- # Returns the class descending directly from ActiveRecord in the inheritance hierarchy.
+ # Returns the class descending directly from Active Record in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
if klass.superclass == Base || klass.superclass.abstract_class?
klass
@@ -1902,12 +1920,12 @@ module ActiveRecord #:nodoc:
end
end
- # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
+ # Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
def class_name_of_active_record_descendant(klass) #:nodoc:
klass.base_class.name
end
- # Accepts an array, hash, or string of sql conditions and sanitizes
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
@@ -1923,7 +1941,7 @@ module ActiveRecord #:nodoc:
end
alias_method :sanitize_sql, :sanitize_sql_for_conditions
- # Accepts an array, hash, or string of sql conditions and sanitizes
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
# { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
def sanitize_sql_for_assignment(assignments)
@@ -1939,7 +1957,7 @@ module ActiveRecord #:nodoc:
mapping.first.is_a?(Array) ? mapping : [mapping]
end
- # Accepts a hash of sql conditions and replaces those attributes
+ # Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a +composed_of+ relationship with their expanded
# aggregate attribute values.
# Given:
@@ -2012,7 +2030,7 @@ module ActiveRecord #:nodoc:
end
# Accepts an array of conditions. The array has each value
- # sanitized and interpolated into the sql statement.
+ # sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
statement, *values = ary
@@ -2133,7 +2151,9 @@ module ActiveRecord #:nodoc:
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
- # Returns a cache key that can be used to identify this record. Examples:
+ # Returns a cache key that can be used to identify this record.
+ #
+ # ==== Examples
#
# Product.new.cache_key # => "products/new"
# Product.find(5).cache_key # => "products/5" (updated_at not available)
@@ -2489,13 +2509,13 @@ module ActiveRecord #:nodoc:
id
end
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
- # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
- # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
+ # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
# Message class in that example.
def ensure_proper_type
unless self.class.descends_from_active_record?
- write_attribute(self.class.inheritance_column, store_full_sti_class ? self.class.name : self.class.name.demodulize)
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
end
end
@@ -2563,7 +2583,7 @@ module ActiveRecord #:nodoc:
self.class.connection.quote(value, column)
end
- # Interpolate custom sql string in instance context.
+ # Interpolate custom SQL string in instance context.
# Optional record argument is meant for custom insert_sql.
def interpolate_sql(sql, record = nil)
instance_eval("%@#{sql.gsub('@', '\@')}@")
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 3c5caefe3b..10e8330d1c 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -46,28 +46,28 @@ module ActiveRecord
calculate(:count, *construct_count_options_from_args(*args))
end
- # Calculates the average value on a given column. The value is returned as a float. See #calculate for examples with options.
+ # Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options.
#
# Person.average('age')
def average(column_name, options = {})
calculate(:avg, column_name, options)
end
- # Calculates the minimum value on a given column. The value is returned with the same data type of the column. See #calculate for examples with options.
+ # Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
#
# Person.minimum('age')
def minimum(column_name, options = {})
calculate(:min, column_name, options)
end
- # Calculates the maximum value on a given column. The value is returned with the same data type of the column. See #calculate for examples with options.
+ # Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
#
# Person.maximum('age')
def maximum(column_name, options = {})
calculate(:max, column_name, options)
end
- # Calculates the sum of values on a given column. The value is returned with the same data type of the column. See #calculate for examples with options.
+ # Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
#
# Person.sum('age')
def sum(column_name, options = {})
@@ -245,12 +245,14 @@ module ActiveRecord
options.assert_valid_keys(CALCULATIONS_OPTIONS)
end
- # Converts a given key to the value that the database adapter returns as
- # a usable column name.
- # users.id #=> users_id
- # sum(id) #=> sum_id
- # count(distinct users.id) #=> count_distinct_users_id
- # count(*) #=> count_all
+ # Converts the given keys to the value that the database adapter returns as
+ # a usable column name:
+ #
+ # column_alias_for("users.id") # => "users_id"
+ # column_alias_for("sum(id)") # => "sum_id"
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
+ # column_alias_for("count(*)") # => "count_all"
+ # column_alias_for("count", "id") # => "count_id"
def column_alias_for(*keys)
connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a469af682b..6c2f65df05 100755
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -50,7 +50,7 @@ module ActiveRecord
#
# == Inheritable callback queues
#
- # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
# Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
# hierarchy. Example:
#
@@ -161,7 +161,7 @@ module ActiveRecord
# == <tt>before_validation*</tt> returning statements
#
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
- # If <tt>Base#save!</tt> is called it will raise a +RecordNotSaved+ exception.
+ # If Base#save! is called it will raise a RecordNotSaved exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 34627dfaf9..2a8807fb78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -193,7 +193,7 @@ module ActiveRecord
# :database => "path/to/dbfile"
# )
#
- # Also accepts keys as strings (for parsing from yaml for example):
+ # Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
# "adapter" => "sqlite",
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 589acd3945..5358491cde 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -29,7 +29,7 @@ module ActiveRecord
end
# Returns an array of arrays containing the field values.
- # Order is the same as that returned by #columns.
+ # Order is the same as that returned by +columns+.
def select_rows(sql, name = nil)
raise NotImplementedError, "select_rows is an abstract method"
end
@@ -93,7 +93,7 @@ module ActiveRecord
# done if the transaction block raises an exception or returns false.
def rollback_db_transaction() end
- # Alias for #add_limit_offset!.
+ # Alias for <tt>add_limit_offset!</tt>.
def add_limit!(sql, options)
add_limit_offset!(sql, options) if options
end
@@ -106,11 +106,16 @@ module ActiveRecord
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
def add_limit_offset!(sql, options)
if limit = options[:limit]
- sql << " LIMIT #{limit}"
+ sql << " LIMIT #{sanitize_limit(limit)}"
if offset = options[:offset]
- sql << " OFFSET #{offset}"
+ sql << " OFFSET #{offset.to_i}"
end
end
+ sql
+ end
+
+ def sanitize_limit(limit)
+ limit.to_s[/,/] ? limit.split(',').map{ |i| i.to_i }.join(',') : limit.to_i
end
# Appends a locking clause to an SQL statement.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index fdb18b234c..f968b9b173 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -16,9 +16,9 @@ module ActiveRecord
# Instantiates a new column in the table.
#
- # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
- # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
- # +sql_type+ is only used to extract the column's length, if necessary. For example, <tt>company_name varchar(<b>60</b>)</tt>.
+ # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
+ # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
+ # +sql_type+ is only used to extract the column's length, if necessary. For example +60+ in <tt>company_name varchar(60)</tt>.
# +null+ determines if this column allows +NULL+ values.
def initialize(name, default, sql_type = nil, null = true)
@name, @sql_type, @null = name, sql_type, null
@@ -92,7 +92,7 @@ module ActiveRecord
# Returns the human name of the column name.
#
# ===== Examples
- # Column.new('sales_stage', ...).human_name #=> 'Sales stage'
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
def human_name
Base.human_attribute_name(@name)
end
@@ -270,7 +270,7 @@ module ActiveRecord
end
# Represents a SQL table in an abstract way.
- # Columns are stored as a ColumnDefinition in the #columns attribute.
+ # Columns are stored as a ColumnDefinition in the +columns+ attribute.
class TableDefinition
attr_accessor :columns
@@ -350,28 +350,28 @@ module ActiveRecord
# == Examples
# # Assuming td is an instance of TableDefinition
# td.column(:granted, :boolean)
- # #=> granted BOOLEAN
+ # # granted BOOLEAN
#
# td.column(:picture, :binary, :limit => 2.megabytes)
- # #=> picture BLOB(2097152)
+ # # => picture BLOB(2097152)
#
# td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
- # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
+ # # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
#
- # def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
- # #=> bill_gates_money DECIMAL(15,2)
+ # td.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
+ # # => bill_gates_money DECIMAL(15,2)
#
- # def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
- # #=> sensor_reading DECIMAL(30,20)
+ # td.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
+ # # => sensor_reading DECIMAL(30,20)
#
# # While <tt>:scale</tt> defaults to zero on most databases, it
# # probably wouldn't hurt to include it.
- # def.column(:huge_integer, :decimal, :precision => 30)
- # #=> huge_integer DECIMAL(30)
+ # td.column(:huge_integer, :decimal, :precision => 30)
+ # # => huge_integer DECIMAL(30)
#
# == Short-hand examples
#
- # Instead of calling column directly, you can also work with the short-hand definitions for the default types.
+ # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
# They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
# in a single statement.
#
@@ -395,7 +395,7 @@ module ActiveRecord
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
- # TableDefinition#timestamps that'll add created_at and updated_at as datetimes.
+ # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of options, these will be
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 ac24e920fe..a2a1bb8c82 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -32,7 +32,7 @@ module ActiveRecord
def columns(table_name, name = nil) end
# Creates a new table
- # There are two ways to work with #create_table. You can use the block
+ # There are two ways to work with +create_table+. You can use the block
# form or the regular form, like this:
#
# === Block form
@@ -302,7 +302,7 @@ module ActiveRecord
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
migrated = select_values("SELECT version FROM #{sm_table}")
- migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n")
+ migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
end
# Should not be called normally, but this operation is non-destructive.
@@ -372,7 +372,7 @@ module ActiveRecord
def add_column_options!(sql, options) #:nodoc:
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
- # must explcitly check for :null to allow change_column to work on migrations
+ # must explicitly check for :null to allow change_column to work on migrations
if options.has_key? :null
if options[:null] == false
sql << " NOT NULL"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 8c286f64db..f48b107a2a 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -128,15 +128,11 @@ module ActiveRecord
protected
def log(sql, name)
if block_given?
- if @logger and @logger.debug?
- result = nil
- seconds = Benchmark.realtime { result = yield }
- @runtime += seconds
- log_info(sql, name, seconds)
- result
- else
- yield
- end
+ result = nil
+ seconds = Benchmark.realtime { result = yield }
+ @runtime += seconds
+ log_info(sql, name, seconds)
+ result
else
log_info(sql, name, 0)
nil
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index f00a2c8950..653b45021d 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -336,10 +336,11 @@ module ActiveRecord
def add_limit_offset!(sql, options) #:nodoc:
if limit = options[:limit]
+ limit = sanitize_limit(limit)
unless offset = options[:offset]
sql << " LIMIT #{limit}"
else
- sql << " LIMIT #{offset}, #{limit}"
+ sql << " LIMIT #{offset.to_i}, #{limit}"
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2ec2d80af4..7dc7398b0a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -776,7 +776,7 @@ module ActiveRecord
# Returns an ORDER BY clause for the passed order option.
#
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
- # by wrapping the sql as a sub-select and ordering in that query.
+ # by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
@@ -805,7 +805,7 @@ module ActiveRecord
end
private
- # The internal PostgreSQL identifer of the money data type.
+ # The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# Connects to a PostgreSQL server and sets up the adapter depending on the
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 8abbc6d0a4..51cfd10e5c 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -214,6 +214,10 @@ module ActiveRecord
end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
+ raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
+ end
+
super(table_name, column_name, type, options)
# See last paragraph on http://www.sqlite.org/lang_altertable.html
execute "VACUUM"
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index 6034963811..2917f24c20 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -40,9 +40,10 @@ module ActiveRecord
base.alias_method_chain :save, :dirty
base.alias_method_chain :save!, :dirty
base.alias_method_chain :update, :dirty
+ base.alias_method_chain :reload, :dirty
base.superclass_delegating_accessor :partial_updates
- base.partial_updates = false
+ base.partial_updates = true
end
# Do any attributes have unsaved changes?
@@ -61,7 +62,7 @@ module ActiveRecord
changed_attributes.keys
end
- # Map of changed attrs => [original value, new value]
+ # Map of changed attrs => [original value, new value].
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { 'name' => ['bill', 'bob'] }
@@ -84,28 +85,35 @@ module ActiveRecord
status
end
+ # <tt>reload</tt> the record and clears changed attributes.
+ def reload_with_dirty(*args) #:nodoc:
+ record = reload_without_dirty(*args)
+ changed_attributes.clear
+ record
+ end
+
private
- # Map of change attr => original value.
+ # Map of change <tt>attr => original value</tt>.
def changed_attributes
@changed_attributes ||= {}
end
- # Handle *_changed? for method_missing.
+ # Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr)
changed_attributes.include?(attr)
end
- # Handle *_change for method_missing.
+ # Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle *_was for method_missing.
+ # Handle <tt>*_was</tt> for +method_missing+.
def attribute_was(attr)
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
- # Handle *_will_change! for method_missing.
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
end
@@ -117,14 +125,7 @@ module ActiveRecord
# The attribute already has an unsaved change.
unless changed_attributes.include?(attr)
old = clone_attribute_value(:read_attribute, attr)
-
- # Remember the original value if it's different.
- typecasted = if column = column_for_attribute(attr)
- column.type_cast(value)
- else
- value
- end
- changed_attributes[attr] = old unless old == typecasted
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
end
# Carry on.
@@ -138,5 +139,20 @@ module ActiveRecord
update_without_dirty
end
end
+
+ def field_changed?(attr, old, value)
+ if column = column_for_attribute(attr)
+ if column.type == :integer && column.null && old.nil?
+ # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ value = nil if value.blank?
+ else
+ value = column.type_cast(value)
+ end
+ end
+
+ old != value
+ end
+
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index ac06cdbe43..c4cbe5d52f 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -33,8 +33,8 @@ end
#
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
# by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
-# put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
-# "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
+# put your files in <tt><your-rails-app>/test/fixtures/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
+# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
#
# rubyonrails:
# id: 1
@@ -67,7 +67,8 @@ end
# = CSV fixtures
#
# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
-# in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
+# in a single file, but instead end with the <tt>.csv</tt> file extension
+# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
#
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
@@ -93,11 +94,11 @@ end
# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
-# put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
-# model).
+# put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
+# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
#
# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
-# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the
+# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
# above example might look like:
#
# web_sites/google
@@ -138,20 +139,20 @@ end
#
# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
# of the test case. It is named after the symbol... so, in our example, there would be a hash available called
-# @web_sites. This is where the "fixture name" comes into play.
+# <tt>@web_sites</tt>. This is where the "fixture name" comes into play.
#
-# On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
-# So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
+# On top of that, each record is automatically "found" (using <tt>Model.find(id)</tt>) and placed in the instance variable of its name.
+# So for the YAML fixtures, we'd get <tt>@rubyonrails</tt> and <tt>@google</tt>, which could be interrogated using regular Active Record semantics:
#
# # test if the object created from the fixture data has the same attributes as the data itself
# def test_find
# assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
# end
#
-# As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
-# "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
-# from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
-# fixtures available as instance variables @web_site_1 and @web_site_2.
+# As seen above, the data hash created from the YAML fixtures would have <tt>@web_sites["rubyonrails"]["url"]</tt> return
+# "http://www.rubyonrails.org" and <tt>@web_sites["google"]["name"]</tt> would return "Google". The same fixtures, but loaded
+# from a CSV fixture file, would be accessible via <tt>@web_sites["web_site_1"]["name"] == "Ruby on Rails"</tt> and have the individual
+# fixtures available as instance variables <tt>@web_site_1</tt> and <tt>@web_site_2</tt>.
#
# If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
#
@@ -184,7 +185,7 @@ end
#
# This will create 1000 very simple YAML fixtures.
#
-# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
+# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
@@ -257,7 +258,7 @@ end
# reginald: # generated id: 324201669
# name: Reginald the Pirate
#
-# ActiveRecord looks at the fixture's model class, discovers the correct
+# Active Record looks at the fixture's model class, discovers the correct
# primary key, and generates it right before inserting the fixture
# into the database.
#
@@ -267,7 +268,7 @@ end
# == Label references for associations (belongs_to, has_one, has_many)
#
# Specifying foreign keys in fixtures can be very fragile, not to
-# mention difficult to read. Since ActiveRecord can figure out the ID of
+# mention difficult to read. Since Active Record can figure out the ID of
# any fixture from its label, you can specify FK's by label instead of ID.
#
# === belongs_to
@@ -304,15 +305,15 @@ end
# name: George the Monkey
# pirate: reginald
#
-# Pow! All is made clear. ActiveRecord reflects on the fixture's model class,
+# Pow! All is made clear. Active Record reflects on the fixture's model class,
# finds all the +belongs_to+ associations, and allows you to specify
# a target *label* for the *association* (monkey: george) rather than
-# a target *id* for the *FK* (monkey_id: 1).
+# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
#
# ==== Polymorphic belongs_to
#
# Supporting polymorphic relationships is a little bit more complicated, since
-# ActiveRecord needs to know what type your association is pointing at. Something
+# Active Record needs to know what type your association is pointing at. Something
# like this should look familiar:
#
# ### in fruit.rb
@@ -332,7 +333,7 @@ end
# apple:
# eater: george (Monkey)
#
-# Just provide the polymorphic target type and ActiveRecord will take care of the rest.
+# Just provide the polymorphic target type and Active Record will take care of the rest.
#
# === has_and_belongs_to_many
#
@@ -395,15 +396,15 @@ end
#
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
# on George's fixture, but we could've just as easily specified a list
-# of monkeys on each fruit. As with +belongs_to+, ActiveRecord reflects on
+# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
# the fixture's model class and discovers the +has_and_belongs_to_many+
# associations.
#
# == Autofilled timestamp columns
#
-# If your table/model specifies any of ActiveRecord's
-# standard timestamp columns (created_at, created_on, updated_at, updated_on),
-# they will automatically be set to Time.now.
+# If your table/model specifies any of Active Record's
+# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
+# they will automatically be set to <tt>Time.now</tt>.
#
# If you've set specific values, they'll be left alone.
#
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 65f88cfdc7..c66034d18b 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -107,20 +107,20 @@ module ActiveRecord
end
# Is optimistic locking enabled for this table? Returns true if the
- # #lock_optimistically flag is set to true (which it is, by default)
- # and the table includes the #locking_column column (defaults to
- # lock_version).
+ # +lock_optimistically+ flag is set to true (which it is, by default)
+ # and the table includes the +locking_column+ column (defaults to
+ # +lock_version+).
def locking_enabled?
lock_optimistically && columns_hash[locking_column]
end
- # Set the column to use for optimistic locking. Defaults to lock_version.
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
def set_locking_column(value = nil, &block)
define_attr_method :locking_column, value, &block
value
end
- # The version column used for optimistic locking. Defaults to lock_version.
+ # The version column used for optimistic locking. Defaults to +lock_version+.
def locking_column
reset_locking_column
end
@@ -130,12 +130,12 @@ module ActiveRecord
connection.quote_column_name(locking_column)
end
- # Reset the column used for optimistic locking back to the lock_version default.
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
def reset_locking_column
set_locking_column DEFAULT_LOCKING_COLUMN
end
- # make sure the lock version column gets updated when counters are
+ # Make sure the lock version column gets updated when counters are
# updated.
def update_counters_with_lock(id, counters)
counters = counters.merge(locking_column => 1) if locking_enabled?
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 5cc9f4e197..b47b01e99a 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -208,7 +208,7 @@ module ActiveRecord
#
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
#
- # You can also insert your own messages and benchmarks by using the #say_with_time
+ # You can also insert your own messages and benchmarks by using the +say_with_time+
# method:
#
# def self.up
@@ -377,7 +377,7 @@ module ActiveRecord
end
def proper_table_name(name)
- # Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
+ # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
end
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index d43ebefc3b..b0c8a8b815 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -2,9 +2,7 @@ module ActiveRecord
module NamedScope
# All subclasses of ActiveRecord::Base have two named_scopes:
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
- # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
- #
- # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
#
# These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
# intermediate values (scopes) around as first-class objects is convenient.
@@ -41,7 +39,7 @@ module ActiveRecord
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
- # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
+ # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
# <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
@@ -102,7 +100,13 @@ module ActiveRecord
class Scope
attr_reader :proxy_scope, :proxy_options
- [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
+
+ [].methods.each do |m|
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
+ delegate m, :to => :proxy_found
+ end
+ end
+
delegate :scopes, :with_scope, :to => :proxy_scope
def initialize(proxy_scope, options, &block)
@@ -115,6 +119,26 @@ module ActiveRecord
load_found; self
end
+ def first(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.first(*args)
+ else
+ find(:first, *args)
+ end
+ end
+
+ def last(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.last(*args)
+ else
+ find(:last, *args)
+ end
+ end
+
+ def empty?
+ @found ? @found.empty? : count.zero?
+ end
+
protected
def proxy_found
@found || load_found
@@ -136,4 +160,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 2b0728fc25..6e55e36b7d 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# # Same as above, just using explicit class references
# ActiveRecord::Base.observers = Cacher, GarbageCollector
#
- # Note: Setting this does not instantiate the observers yet. #instantiate_observers is
+ # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
# called during startup, and before each development request.
def observers=(*observers)
@observers = observers.flatten
@@ -30,7 +30,7 @@ module ActiveRecord
@observers ||= []
end
- # Instantiate the global ActiveRecord observers
+ # Instantiate the global Active Record observers.
def instantiate_observers
return if @observers.blank?
@observers.each do |observer|
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index d6b254fcf9..8a32cf1ca2 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -30,8 +30,8 @@ module ActiveRecord
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
- # database definition DSL to build up your schema (#create_table,
- # #add_index, etc.).
+ # database definition DSL to build up your schema (+create_table+,
+ # +add_index+, etc.).
#
# The +info+ hash is optional, and if given is used to define metadata
# about the current schema (currently, only the schema's version):
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 826662d3ee..b90ed88c6b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -38,7 +38,7 @@ module ActiveRecord
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead of editing this file,
-# please use the migrations feature of ActiveRecord to incrementally modify your database, and
+# please use the migrations feature of Active Record to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 2d0887ecf0..d171b742f5 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -273,14 +273,14 @@ module ActiveRecord #:nodoc:
end
# There is a significant speed improvement if the value
- # does not need to be escaped, as #tag! escapes all values
+ # does not need to be escaped, as <tt>tag!</tt> escapes all values
# to ensure that valid XML is generated. For known binary
# values, it is at least an order of magnitude faster to
# Base64 encode binary values and directly put them in the
# output XML than to pass the original value or the Base64
- # encoded value to the #tag! method. It definitely makes
+ # encoded value to the <tt>tag!</tt> method. It definitely makes
# no sense to Base64 encode the value and then give it to
- # #tag!, since that just adds additional overhead.
+ # <tt>tag!</tt>, since that just adds additional overhead.
def needs_encoding?
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 13cb5f3f48..3b6835762c 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -30,9 +30,9 @@ module ActiveRecord
# 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 ActiveRecord classes in a single transaction
+ # == Different Active Record classes in a single transaction
#
- # Though the transaction class method is called on some ActiveRecord class,
+ # 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.
# In this example a <tt>Balance</tt> record is transactionally saved even
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index b3a75121ed..52805ea851 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -1,5 +1,5 @@
module ActiveRecord
- # Raised by save! and create! when the record is invalid. Use the
+ # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
# +record+ method to retrieve the record which did not validate.
# begin
# complex_operation_that_calls_save!_internally
@@ -52,7 +52,7 @@ module ActiveRecord
# Adds an error to the base object instead of any particular attribute. This is used
# to report errors that don't tie to any specific attribute, but rather to the object
# as a whole. These error messages don't get prepended with any field name when iterating
- # with each_full, so they should be complete sentences.
+ # with +each_full+, so they should be complete sentences.
def add_to_base(msg)
add(:base, msg)
end
@@ -97,7 +97,7 @@ module ActiveRecord
!@errors[attribute.to_s].nil?
end
- # Returns nil, if no errors are associated with the specified +attribute+.
+ # Returns +nil+, if no errors are associated with the specified +attribute+.
# Returns the error message, if one error is associated with the specified +attribute+.
# Returns an array of error messages, if more than one error is associated with the specified +attribute+.
#
@@ -118,7 +118,7 @@ module ActiveRecord
alias :[] :on
- # Returns errors assigned to the base object through add_to_base according to the normal rules of on(attribute).
+ # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
def on_base
on(:base)
end
@@ -131,15 +131,15 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" } # =>
- # name - is too short (minimum is 5 characters)
- # name - can't be blank
- # address - can't be blank
+ # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
+ # # => name - is too short (minimum is 5 characters)
+ # # name - can't be blank
+ # # address - can't be blank
def each
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
end
- # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
+ # Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
# through iteration as "First name can't be empty".
#
# class Company < ActiveRecord::Base
@@ -148,10 +148,10 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.each_full{|msg| puts msg } # =>
- # Name is too short (minimum is 5 characters)
- # Name can't be blank
- # Address can't be blank
+ # company.errors.each_full{|msg| puts msg }
+ # # => Name is too short (minimum is 5 characters)
+ # # Name can't be blank
+ # # Address can't be blank
def each_full
full_messages.each { |msg| yield msg }
end
@@ -164,8 +164,8 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ # company.errors.full_messages
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages
full_messages = []
@@ -209,13 +209,13 @@ module ActiveRecord
# end
#
# company = Company.create(:address => '123 First St.')
- # company.errors.to_xml # =>
- # <?xml version="1.0" encoding="UTF-8"?>
- # <errors>
- # <error>Name is too short (minimum is 5 characters)</error>
- # <error>Name can't be blank</error>
- # <error>Address can't be blank</error>
- # </errors>
+ # company.errors.to_xml
+ # # => <?xml version="1.0" encoding="UTF-8"?>
+ # # <errors>
+ # # <error>Name is too short (minimum is 5 characters)</error>
+ # # <error>Name can't be blank</error>
+ # # <error>Address can't be blank</error>
+ # # </errors>
def to_xml(options={})
options[:root] ||= "errors"
options[:indent] ||= 2
@@ -261,7 +261,7 @@ module ActiveRecord
# person.errors.on "phone_number" # => "has invalid format"
# person.errors.each_full { |msg| puts msg }
# # => "Last name can't be empty\n" +
- # "Phone number has invalid format"
+ # # "Phone number has invalid format"
#
# person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
# person.save # => true (and person is now saved in the database)
@@ -301,7 +301,7 @@ module ActiveRecord
:odd => 'odd?', :even => 'even?' }.freeze
# Adds a validation method or block to the class. This is useful when
- # overriding the #validate instance method becomes too unwieldly and
+ # overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
#
# This can be done with a symbol pointing to a method:
@@ -326,7 +326,7 @@ module ActiveRecord
# end
# end
#
- # This usage applies to #validate_on_create and #validate_on_update as well.
+ # This usage applies to +validate_on_create+ and +validate_on_update+ as well.
# Validates each attribute against a block.
#
@@ -692,7 +692,7 @@ module ActiveRecord
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
+ record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 1463e84764..aaadef9979 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb
index ddf3e82162..2a42dc3517 100644
--- a/activerecord/test/cases/active_schema_test_mysql.rb
+++ b/activerecord/test/cases/active_schema_test_mysql.rb
@@ -40,47 +40,56 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
def test_add_timestamps
- #we need to actually modify some data, so we make execute to point to the original method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
- alias_method :execute_with_stub, :execute
- alias_method :execute, :execute_without_stub
- end
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
- ActiveRecord::Base.connection.add_timestamps :delete_me
- assert_equal ActiveRecord::Base.connection.execute("SHOW FIELDS FROM delete_me where FIELD='updated_at' AND TYPE='datetime'").num_rows, 1
- assert_equal ActiveRecord::Base.connection.execute("SHOW FIELDS FROM delete_me where FIELD='created_at' AND TYPE='datetime'").num_rows, 1
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- #before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
- alias_method :execute, :execute_with_stub
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ end
+ ActiveRecord::Base.connection.add_timestamps :delete_me
+ assert column_present?('delete_me', 'updated_at', 'datetime')
+ assert column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
end
end
def test_remove_timestamps
- #we need to actually modify some data, so we make execute to point to the original method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
- alias_method :execute_with_stub, :execute
- alias_method :execute, :execute_without_stub
- end
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- t.timestamps
- end
- ActiveRecord::Base.connection.remove_timestamps :delete_me
- assert_equal ActiveRecord::Base.connection.execute("SHOW FIELDS FROM delete_me where FIELD='updated_at' AND TYPE='datetime'").num_rows, 0
- assert_equal ActiveRecord::Base.connection.execute("SHOW FIELDS FROM delete_me where FIELD='created_at' AND TYPE='datetime'").num_rows, 0
- ensure
- ActiveRecord::Base.connection.drop_table :delete_me rescue nil
- #before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
- alias_method :execute, :execute_with_stub
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps
+ end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me
+ assert !column_present?('delete_me', 'updated_at', 'datetime')
+ assert !column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
end
end
-
private
+ def with_real_execute
+ #we need to actually modify some data, so we make execute point to the original method
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ alias_method :execute_with_stub, :execute
+ alias_method :execute, :execute_without_stub
+ end
+ yield
+ ensure
+ #before finishing, we restore the alias to the mock-up method
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ alias_method :execute, :execute_with_stub
+ end
+ end
+
+
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
end
+
+ def column_present?(table_name, column_name, type)
+ results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
+ results.first && results.first['Type'] == type
+ end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 91504af901..11f9870534 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -104,4 +104,24 @@ class AdapterTest < ActiveRecord::TestCase
end
end
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
+ sql_inject = "1 select * from schema"
+ assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject)
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ else
+ assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
+
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
+ sql_inject = "1, 7 procedure help()"
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7)
+ else
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index b8ec9117af..e0da8bfb7a 100644..100755
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -12,6 +12,8 @@ require 'models/author'
require 'models/tag'
require 'models/tagging'
require 'models/comment'
+require 'models/sponsor'
+require 'models/member'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -54,8 +56,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
original_proxy = citibank.firm
citibank.firm = another_firm
- assert_equal first_firm.object_id, original_proxy.object_id
- assert_equal another_firm.object_id, citibank.firm.object_id
+ assert_equal first_firm.object_id, original_proxy.target.object_id
+ assert_equal another_firm.object_id, citibank.firm.target.object_id
end
def test_creating_the_belonging_object
@@ -92,6 +94,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
end
+ def test_with_select
+ assert_equal Company.find(2).firm_with_select.attributes.size, 1
+ assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1
+ end
+
def test_belongs_to_counter
debate = Topic.create("title" => "debate")
assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
@@ -376,5 +383,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
assert companies(:first_client).readonly_firm.readonly?
end
-
+
+ def test_polymorphic_assignment_foreign_type_field_updating
+ # should update when assigning a saved record
+ sponsor = Sponsor.new
+ member = Member.create
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+
+ # should update when assigning a new record
+ sponsor = Sponsor.new
+ member = Member.new
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+ end
+
+ def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records
+ sponsor = Sponsor.new
+ saved_member = Member.create
+ new_member = Member.new
+
+ sponsor.sponsorable = saved_member
+ assert_equal saved_member.id, sponsor.sponsorable_id
+
+ sponsor.sponsorable = new_member
+ assert_equal nil, sponsor.sponsorable_id
+ end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 64565141f9..294b993c55 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -401,6 +401,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
project = projects(:active_record)
+ project.developers.class # force load target
+
developer = project.developers.first
assert_no_queries do
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 9e26e2ad58..dbfa025efb 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -32,6 +32,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, Firm.find(:first).plain_clients.count
end
+ def test_counting_with_empty_hash_conditions
+ assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {})
+ end
+
def test_counting_with_single_conditions
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
end
@@ -346,6 +350,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
+ company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
@@ -356,6 +361,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
assert_equal 2, new_clients.size
+ company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
@@ -818,6 +824,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
firm = companies(:first_firm)
+ firm.clients.class # force load target
+
client = firm.clients.first
assert_no_queries do
@@ -857,4 +865,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert ! firm.clients.include?(client)
end
+ def test_calling_first_or_last_on_association_should_not_load_association
+ firm = companies(:first_firm)
+ firm.clients.first
+ firm.clients.last
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+ assert firm.clients.loaded?
+
+ assert_no_queries do
+ firm.clients.first
+ assert_equal 2, firm.clients.first(2).size
+ firm.clients.last
+ assert_equal 2, firm.clients.last(2).size
+ end
+ end
+
+ def test_calling_first_or_last_on_existing_record_with_build_should_load_association
+ firm = companies(:first_firm)
+ firm.clients.build(:name => 'Foo')
+ assert !firm.clients.loaded?
+
+ assert_queries 1 do
+ firm.clients.first
+ firm.clients.last
+ end
+
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_new_record_should_not_run_queries
+ firm = Firm.new
+
+ assert_no_queries do
+ firm.clients.first
+ firm.clients.last
+ end
+ end
+
+ def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+
+ assert_queries 2 do
+ assert firm.clients.loaded?
+ firm.clients.first(:order => 'name')
+ firm.clients.last(:order => 'name')
+ end
+ end
+
+ def test_calling_first_or_last_with_integer_on_association_should_load_association
+ firm = companies(:first_firm)
+
+ assert_queries 1 do
+ firm.clients.first(2)
+ firm.clients.last(2)
+ end
+
+ assert firm.clients.loaded?
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 5561361bca..05155f6303 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -59,6 +59,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
# * 2 new records = 4
# + 1 query to save the actual post = 5
assert_queries(5) do
+ posts(:thinking).body += '-changed'
posts(:thinking).save
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 9e99caa7b7..abc7ee7e9d 100644..100755
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -24,6 +24,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_queries(0) { firms.each(&:account) }
end
+ def test_with_select
+ assert_equal Firm.find(1).account_with_select.attributes.size, 2
+ assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
+ end
+
def test_can_marshal_has_one_association_with_nil_target
firm = Firm.new
assert_nothing_raised do
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 952ea63706..9e79d9c8a1 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -664,6 +664,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_include_uses_array_include_after_loaded
david = authors(:david)
+ david.categories.class # force load target
+
category = david.categories.first
assert_no_queries do
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index d8fe98bf57..59349dd7cf 100755
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -99,12 +99,12 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
assert_equal david, david.posts.proxy_owner
assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection
- david.posts.first # force load target
+ david.posts.class # force load target
assert_equal david.posts, david.posts.proxy_target
assert_equal david, david.posts_with_extension.testing_proxy_owner
assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection
- david.posts_with_extension.first # force load target
+ david.posts_with_extension.class # force load target
assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target
end
@@ -160,6 +160,18 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal 1, developer.reload.audit_logs.size
end
+ def test_create_via_association_with_block
+ post = authors(:david).posts.create(:title => "New on Edge") {|p| p.body = "More cool stuff!"}
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
+ def test_create_with_bang_via_association_with_block
+ post = authors(:david).posts.create!(:title => "New on Edge") {|p| p.body = "More cool stuff!"}
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
def test_failed_reload_returns_nil
p = setup_dangling_association
assert_nil p.author.reload
diff --git a/activerecord/test/cases/datatype_test_postgresql.rb b/activerecord/test/cases/datatype_test_postgresql.rb
index 41726ce518..bff092b5d7 100644
--- a/activerecord/test/cases/datatype_test_postgresql.rb
+++ b/activerecord/test/cases/datatype_test_postgresql.rb
@@ -30,8 +30,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )")
@first_array = PostgresqlArray.find(1)
- @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('$567.89')")
- @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-$567.89')")
+ @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('567.89'::money)")
+ @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-567.89'::money)")
@first_money = PostgresqlMoney.find(1)
@second_money = PostgresqlMoney.find(2)
@@ -143,11 +143,11 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
def test_update_money
- new_value = 123.45
+ new_value = BigDecimal.new('123.45')
assert @first_money.wealth = new_value
assert @first_money.save
assert @first_money.reload
- assert_equal @first_money.wealth, new_value
+ assert_equal new_value, @first_money.wealth
end
def test_update_number
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index bd19ffcc29..2ea85417da 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -5,7 +5,7 @@ require 'models/entrant'
class DefaultTest < ActiveRecord::TestCase
def test_nil_defaults_for_not_null_columns
column_defaults =
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) && Mysql.client_version < 50051
{ 'id' => nil, 'name' => '', 'course_id' => nil }
else
{ 'id' => nil, 'name' => nil, 'course_id' => nil }
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 1266eb5036..c011ffaf57 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -44,6 +44,16 @@ class DirtyTest < ActiveRecord::TestCase
assert_nil pirate.catchphrase_change
end
+ def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
+ pirate = Pirate.new
+
+ ["", nil].each do |value|
+ pirate.parrot_id = value
+ assert !pirate.parrot_id_changed?
+ assert_nil pirate.parrot_id_change
+ end
+ end
+
def test_object_should_be_changed_if_any_attribute_is_changed
pirate = Pirate.new
assert !pirate.changed?
@@ -127,6 +137,14 @@ class DirtyTest < ActiveRecord::TestCase
check_pirate_after_save_failure(pirate)
end
+ def test_reload_should_clear_changed_attributes
+ pirate = Pirate.create!(:catchphrase => "shiver me timbers")
+ pirate.catchphrase = "*hic*"
+ assert pirate.changed?
+ pirate.reload
+ assert !pirate.changed?
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 5c0f0e2ef1..80936d51f3 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -867,7 +867,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3)
+ posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id')
assert_equal 3, posts.size
assert_equal [0, 1, 1], posts.map(&:author_id).sort
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 27394924a1..f09b617e6d 100755
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -5,7 +5,23 @@ require 'models/subscriber'
class InheritanceTest < ActiveRecord::TestCase
fixtures :companies, :projects, :subscribers, :accounts
-
+
+ def test_class_with_store_full_sti_class_returns_full_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+ assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_class_without_store_full_sti_class_returns_demodulized_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = false
+ assert_equal 'Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = false
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 4b5bd6c951..1a9a875730 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -50,6 +50,22 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoped_find_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :conditions => "name = 'David'")
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_options_select_replaces_scope_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
+ assert_equal 80000, developer.salary
+ assert !developer.has_attribute?(:name)
+ end
+ end
+
def test_scoped_count
Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
assert_equal 1, Developer.count
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 527856b4c0..f36255e209 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -281,7 +281,7 @@ if ActiveRecord::Base.connection.supports_migrations?
# Do a manual insertion
if current_adapter?(:OracleAdapter)
Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
- elsif current_adapter?(:OpenBaseAdapter)
+ elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
else
Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
@@ -384,7 +384,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_not_equal "Z", bob.moment_of_truth.zone
# US/Eastern is -5 hours from GMT
assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_equal "-05:00", bob.moment_of_truth.zone
+ assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM
assert_equal DateTime::ITALY, bob.moment_of_truth.start
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 30c074c9d8..d890cf7936 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -6,7 +6,7 @@ require 'models/reply'
require 'models/author'
class NamedScopeTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :topics
+ fixtures :posts, :authors, :topics, :comments
def test_implements_enumerable
assert !Topic.find(:all).empty?
@@ -95,7 +95,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_has_many_through_associations_have_access_to_named_scopes
- assert_not_equal Comment.containing_the_letter_e, authors(:david).posts
+ assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
assert !Comment.containing_the_letter_e.empty?
assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e
@@ -118,4 +118,40 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal expected_proxy_options, Topic.approved.proxy_options
end
+ def test_first_and_last_should_support_find_options
+ assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
+ assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
+ end
+
+ def test_first_and_last_should_allow_integers_for_limit
+ assert_equal Topic.base.first(2), Topic.base.to_a.first(2)
+ assert_equal Topic.base.last(2), Topic.base.to_a.last(2)
+ end
+
+ def test_first_and_last_should_not_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_no_queries do
+ topics.first
+ topics.last
+ end
+ end
+
+ def test_first_and_last_find_options_should_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_queries(2) do
+ topics.first(:order => 'title')
+ topics.last(:order => 'title')
+ end
+ end
+
+ def test_empty_should_not_load_results
+ topics = Topic.base
+ assert_queries(2) do
+ topics.empty? # use count query
+ topics.collect # force load
+ topics.empty? # use loaded (no query)
+ end
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c8ee40ea09..8b4d232554 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -159,9 +159,10 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_of_all_associations
- assert_equal 19, Firm.reflect_on_all_associations.size
+ # FIXME these assertions bust a lot
+ assert_equal 20, Firm.reflect_on_all_associations.size
assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
- assert_equal 3, Firm.reflect_on_all_associations(:has_one).size
+ assert_equal 4, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 63f04e3014..06a76eacc3 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -179,6 +179,32 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_sqlite_add_column_in_transaction_raises_statement_invalid
+ return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter)
+
+ # Test first if column creation/deletion works correctly when no
+ # transaction is in place.
+ #
+ # We go back to the connection for the column queries because
+ # Topic.columns is cached and won't report changes to the DB
+
+ assert_nothing_raised do
+ Topic.reset_column_information
+ Topic.connection.add_column('topics', 'stuff', :string)
+ assert Topic.column_names.include?('stuff')
+
+ Topic.reset_column_information
+ Topic.connection.remove_column('topics', 'stuff')
+ assert !Topic.column_names.include?('stuff')
+ end
+
+ # Test now inside a transaction: add_column should raise a StatementInvalid
+ Topic.transaction do
+ assert_raises(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) }
+ raise ActiveRecord::Rollback
+ end
+ end
+
private
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index a4d9da4806..7b71647d25 100755
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -583,6 +583,12 @@ class ValidationsTest < ActiveRecord::TestCase
assert_nil t.errors.on(:title)
end
+ def test_validate_format_with_formatted_message
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %s")
+ t = Topic.create(:title => 'Invalid title')
+ assert_equal "can't be Invalid title", t.errors.on(:title)
+ end
+
def test_validates_inclusion_of
Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index f637490c59..70f83fa8e6 100755
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -47,6 +47,7 @@ class Firm < Company
has_many :readonly_clients, :class_name => 'Client', :readonly => true
has_one :account, :foreign_key => "firm_id", :dependent => :destroy
+ has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
end
@@ -64,6 +65,7 @@ end
class Client < Company
belongs_to :firm, :foreign_key => "client_of"
belongs_to :firm_with_basic_id, :class_name => "Firm", :foreign_key => "firm_id"
+ belongs_to :firm_with_select, :class_name => "Firm", :foreign_key => "firm_id", :select => "id"
belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]
belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true