aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG12
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb1
-rw-r--r--activerecord/lib/active_record/base.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb3
-rw-r--r--activerecord/lib/active_record/persistence.rb14
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb28
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-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.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb4
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb25
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/batches_test.rb10
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb33
-rw-r--r--activerecord/test/cases/connection_management_test.rb95
-rw-r--r--activerecord/test/cases/connection_pool_test.rb8
-rw-r--r--activerecord/test/cases/dirty_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb21
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb27
-rw-r--r--activerecord/test/cases/named_scope_test.rb17
-rw-r--r--activerecord/test/cases/persistence_test.rb86
-rw-r--r--activerecord/test/cases/primary_keys_test.rb9
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb3
35 files changed, 456 insertions, 70 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 6be46349fb..e536d2b408 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,17 @@
*Rails 3.1.0 (unreleased)*
+* ConnectionManagement middleware is changed to clean up the connection pool
+ after the rack body has been flushed.
+
+* Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks.
+ It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of
+ the updated_at column. It should not be called on new records.
+ Example:
+
+ User.first.update_column(:name, "sebastian") # => true
+
+ [Sebastian Martinez]
+
* Associations with a :through option can now use *any* association as the
through or source association, including other associations which have a
:through option and has_and_belongs_to_many associations
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index fcdd31ddea..5f06452247 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,6 +17,11 @@ module ActiveRecord
@primary_key ||= reset_primary_key
end
+ # Returns a quoted version of the primary key name, used to construct SQL statements.
+ def quoted_primary_key
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
+ end
+
def reset_primary_key #:nodoc:
key = self == base_class ? get_primary_key(base_class.name) :
base_class.primary_key
@@ -43,7 +48,12 @@ module ActiveRecord
end
attr_accessor :original_primary_key
- attr_writer :primary_key
+
+ # Attribute writer for the primary key column
+ def primary_key=(value)
+ @quoted_primary_key = nil
+ @primary_key = value
+ end
# Sets the name of the primary key column to use to the given value,
# or (if the value is nil or false) to the value returned by the given
@@ -53,6 +63,7 @@ module ActiveRecord
# set_primary_key "sysid"
# end
def set_primary_key(value = nil, &block)
+ @quoted_primary_key = nil
@primary_key ||= ''
self.original_primary_key = @primary_key
value &&= value.to_s
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 3c4dab304e..7661676f8c 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -32,6 +32,7 @@ module ActiveRecord
@attributes[attr_name] = value
end
end
+ alias_method :raw_write_attribute, :write_attribute
private
# Handle *= for method_missing.
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index b778b0c0f0..fe81c7dc2f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -437,9 +437,10 @@ module ActiveRecord #:nodoc:
self._attr_readonly = []
class << self # Class methods
- delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
+ delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :except, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 4297c26413..b4db1eed18 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -151,6 +151,12 @@ module ActiveRecord
@reserved_connections[current_connection_id] ||= checkout
end
+ # Check to see if there is an active connection in this connection
+ # pool.
+ def active_connection?
+ @reserved_connections.key? current_connection_id
+ end
+
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
@@ -346,6 +352,12 @@ module ActiveRecord
@connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
end
+ # Returns true if there are any active connections among the connection
+ # pools that the ConnectionHandler is managing.
+ def active_connections?
+ connection_pools.values.any? { |pool| pool.active_connection? }
+ end
+
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
@@ -405,18 +417,40 @@ module ActiveRecord
end
class ConnectionManagement
+ class Proxy # :nodoc:
+ attr_reader :body, :testing
+
+ def initialize(body, testing = false)
+ @body = body
+ @testing = testing
+ end
+
+ def each(&block)
+ body.each(&block)
+ end
+
+ def close
+ body.close if body.respond_to?(:close)
+
+ # Don't return connection (and perform implicit rollback) if
+ # this request is a part of integration test
+ ActiveRecord::Base.clear_active_connections! unless testing
+ end
+ end
+
def initialize(app)
@app = app
end
def call(env)
- @app.call(env)
- ensure
- # Don't return connection (and perform implicit rollback) if
- # this request is a part of integration test
- unless env.key?("rack.test")
- ActiveRecord::Base.clear_active_connections!
- end
+ testing = env.key?('rack.test')
+
+ status, headers, body = @app.call(env)
+
+ [status, headers, Proxy.new(body, testing)]
+ rescue
+ ActiveRecord::Base.clear_active_connections! unless testing
+ raise
end
end
end
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 d88720c8bf..bcd3abc08d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -116,7 +116,11 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
- delegate :clear_active_connections!, :clear_reloadable_connections!,
+ def clear_active_connections!
+ connection_handler.clear_active_connections!
+ end
+
+ delegate :clear_reloadable_connections!,
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index ae61d6ce94..32229a8410 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -222,7 +222,7 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = nil) #:nodoc:
+ def tables(name = 'SCHEMA') #:nodoc:
sql = <<-SQL
SELECT name
FROM sqlite_master
@@ -350,7 +350,7 @@ module ActiveRecord
end
def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})").to_hash
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
structure
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index afadbf03ef..d31e321440 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -23,6 +23,9 @@ module ActiveRecord
return unless logger.debug?
payload = event.payload
+
+ return if 'SCHEMA' == payload[:name]
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
sql = payload[:sql].squeeze(' ')
binds = nil
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 17a64b6e86..a916c88348 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -119,6 +119,20 @@ module ActiveRecord
save(:validate => false)
end
+ # Updates a single attribute of an object, without calling save.
+ #
+ # * Validation is skipped.
+ # * Callbacks are skipped.
+ # * updated_at/updated_on column is not updated if that column is available.
+ #
+ def update_column(name, value)
+ name = name.to_s
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
+ raw_write_attribute(name, value)
+ self.class.update_all({ name => value }, self.class.primary_key => id) == 1
+ end
+
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 8e545f9cad..896daf516e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -12,7 +12,7 @@ module ActiveRecord
# These are explicitly delegated to improve performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
- delegate :table_name, :primary_key, :to => :klass
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
attr_reader :table, :klass, :loaded
attr_accessor :extensions
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index bf5a60f458..d52b84179f 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -83,7 +83,7 @@ module ActiveRecord
private
def batch_order
- "#{table_name}.#{primary_key} ASC"
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 25e23a9d55..8fa315bdf3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -123,9 +123,10 @@ module ActiveRecord
end
end
- # Same as #first! but raises RecordNotFound if no record is returned
- def first!(*args)
- self.first(*args) or raise RecordNotFound
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>first!</tt> accepts no arguments.
+ def first!
+ first or raise RecordNotFound
end
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
@@ -142,9 +143,10 @@ module ActiveRecord
end
end
- # Same as #last! but raises RecordNotFound if no record is returned
- def last!(*args)
- self.last(*args) or raise RecordNotFound
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>last!</tt> accepts no arguments.
+ def last!
+ last or raise RecordNotFound
end
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
@@ -181,7 +183,9 @@ module ActiveRecord
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
- relation = select("1").limit(1)
+ join_dependency = construct_join_dependency_for_association_find
+ relation = construct_relation_for_association_find(join_dependency)
+ relation = relation.except(:select).select("1").limit(1)
case id
when Array, Hash
@@ -190,14 +194,13 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- relation.first ? true : false
+ connection.select_value(relation.to_sql) ? true : false
end
protected
def find_with_associations
- including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
@@ -205,6 +208,11 @@ module ActiveRecord
[]
end
+ def construct_join_dependency_for_association_find
+ including = (@eager_load_values + @includes_values).uniq
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ end
+
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 9470e7c6c5..02b7056989 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -62,6 +62,10 @@ module ActiveRecord
relation
end
+ def reorder(*args)
+ except(:order).order(args)
+ end
+
def joins(*args)
return self if args.compact.blank?
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 73d02c9676..f4d14853d3 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
@@ -604,7 +604,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project = SpecialProject.create("name" => "Special Project")
assert developer.save
developer.projects << project
- developer.update_attribute("name", "Bruza")
+ developer.update_column("name", "Bruza")
assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
SELECT count(*) FROM developers_projects
WHERE project_id = #{project.id}
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ad774eb9ce..16d4877fe8 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -639,7 +639,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_delete_all
post = posts(:welcome)
- post.update_attribute(:taggings_with_delete_all_count, post.taggings_count)
+ post.update_column(:taggings_with_delete_all_count, post.taggings_count)
assert_difference "post.reload.taggings_with_delete_all_count", -1 do
post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first)
@@ -648,7 +648,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_destroy
post = posts(:welcome)
- post.update_attribute(:taggings_with_destroy_count, post.taggings_count)
+ post.update_column(:taggings_with_destroy_count, post.taggings_count)
assert_difference "post.reload.taggings_with_destroy_count", -1 do
post.taggings_with_destroy.delete(post.taggings_with_destroy.first)
@@ -787,7 +787,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = Firm.find(:first)
# break the vanilla firm_id foreign key
assert_equal 2, firm.clients.count
- firm.clients.first.update_attribute(:firm_id, nil)
+ firm.clients.first.update_column(:firm_id, nil)
assert_equal 1, firm.clients(true).count
assert_equal 1, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
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 9adaebe924..1efe3420a0 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -286,7 +286,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_destroy
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_destroy_count, post.tags.count)
+ post.update_column(:tags_with_destroy_count, post.tags.count)
assert_difference ['post.reload.taggings_count', 'post.reload.tags_with_destroy_count'], -1 do
posts(:welcome).tags_with_destroy.delete(tag)
@@ -296,7 +296,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_nullify
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_nullify_count, post.tags.count)
+ post.update_column(:tags_with_nullify_count, post.tags.count)
assert_no_difference 'post.reload.taggings_count' do
assert_difference 'post.reload.tags_with_nullify_count', -1 do
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 9ba5549277..968025ade8 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -90,12 +90,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_with_conditions_eager_loading
# conditions on the through table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club
- memberships(:membership_of_favourite_club).update_attribute(:favourite, false)
+ memberships(:membership_of_favourite_club).update_column(:favourite, false)
assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
# conditions on the source table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
- clubs(:moustache_club).update_attribute(:name, "Association of Clean-Shaven Persons")
+ clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons")
assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 1f95b31497..5a7b139030 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -161,7 +161,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_delete_all
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDeleteAll'
post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
old_count = Tagging.count
@@ -172,7 +172,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_destroy
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDestroy'
post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
old_count = Tagging.count
@@ -183,7 +183,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_nullify
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyNullify'
post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
old_count = Tagging.count
@@ -194,7 +194,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_destroy
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneDestroy'
post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
old_count = Tagging.count
@@ -205,7 +205,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_nullify
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneNullify'
post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
old_count = Tagging.count
@@ -707,7 +707,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
- Post.find(post_id).update_attribute :type, class_name
+ Post.find(post_id).update_column :type, class_name
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
klass.set_table_name 'posts'
klass.send(association, association_name, :as => :taggable, :dependent => dependency)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 47b8e48582..04f628a398 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -66,7 +66,7 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
- ShipPart.find(part.id).update_attribute(:name, 'Deck')
+ ShipPart.find(part.id).update_column(:name, 'Deck')
ship.parts.send(:load_target)
assert_equal 'Deck', ship.parts[0].name
end
@@ -170,7 +170,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert !david.projects.loaded?
- david.update_attribute(:created_at, Time.now)
+ david.update_column(:created_at, Time.now)
assert !david.projects.loaded?
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d8638ee776..84f75cc803 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -120,11 +120,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_read_attributes_before_type_cast_on_datetime
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
-
+
record.written_on = "345643456"
assert_equal "345643456", record.written_on_before_type_cast
assert_equal nil, record.written_on
-
+
record.written_on = "2009-10-11 12:13:14"
assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast
assert_equal Time.zone.parse("2009-10-11 12:13:14"), record.written_on
@@ -132,6 +132,27 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_read_attributes_after_type_cast_on_datetime
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ record = @target.new
+
+ date_string = "2011-03-24"
+ time = Time.zone.parse date_string
+
+ record.written_on = date_string
+ assert_equal date_string, record.written_on_before_type_cast
+ assert_equal time, record.written_on
+ assert_equal ActiveSupport::TimeZone[tz], record.written_on.time_zone
+
+ record.save
+ record.reload
+
+ assert_equal time, record.written_on
+ end
+ end
+
def test_hash_content
topic = Topic.new
topic.content = { "one" => 1, "two" => 2 }
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fba7af741d..aeb0b28bab 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -484,7 +484,7 @@ class BasicsTest < ActiveRecord::TestCase
weird.reload
assert_equal 'value', weird.send('a$b')
- weird.update_attribute('a$b', 'value2')
+ weird.update_column('a$b', 'value2')
weird.reload
assert_equal 'value2', weird.send('a$b')
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index dc0e0da4c5..6620464d6a 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -83,4 +83,14 @@ class EachTest < ActiveRecord::TestCase
Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
end
end
+
+ def test_find_in_batches_should_quote_batch_order
+ c = Post.connection
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ Post.find_in_batches(:batch_size => 1) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 1e8ce4fda7..654c4c9010 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -326,8 +326,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select_with_options
Account.update_all("credit_limit = NULL")
- Account.last.update_attribute('credit_limit', 49)
- Account.first.update_attribute('credit_limit', 51)
+ Account.last.update_column('credit_limit', 49)
+ Account.first.update_column('credit_limit', 51)
assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50'])
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
new file mode 100644
index 0000000000..abf317768f
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionHandlerTest < ActiveRecord::TestCase
+ def setup
+ @handler = ConnectionHandler.new
+ @handler.establish_connection 'america', Base.connection_pool.spec
+ @klass = Struct.new(:name).new('america')
+ end
+
+ def test_retrieve_connection
+ assert @handler.retrieve_connection(@klass)
+ end
+
+ def test_active_connections?
+ assert !@handler.active_connections?
+ assert @handler.retrieve_connection(@klass)
+ assert @handler.active_connections?
+ @handler.clear_active_connections!
+ assert !@handler.active_connections?
+ end
+
+ def test_retrieve_connection_pool_with_ar_base
+ assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base)
+ end
+
+ def test_retrieve_connection_pool
+ assert_not_nil @handler.retrieve_connection_pool(@klass)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index c535119972..85871aebdf 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,25 +1,82 @@
require "cases/helper"
-class ConnectionManagementTest < ActiveRecord::TestCase
- def setup
- @env = {}
- @app = stub('App')
- @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
-
- @connections_cleared = false
- ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
- end
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionManagementTest < ActiveRecord::TestCase
+ class App
+ attr_reader :calls
+ def initialize
+ @calls = []
+ end
- test "clears active connections after each call" do
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert @connections_cleared
- end
+ def call(env)
+ @calls << env
+ [200, {}, ['hi mom']]
+ end
+ end
+
+ def setup
+ @env = {}
+ @app = App.new
+ @management = ConnectionManagement.new(@app)
+
+ # make sure we have an active connection
+ assert ActiveRecord::Base.connection
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_app_delegation
+ manager = ConnectionManagement.new(@app)
+
+ manager.call @env
+ assert_equal [@env], @app.calls
+ end
+
+ def test_connections_are_active_after_call
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_body_responds_to_each
+ _, _, body = @management.call(@env)
+ bits = []
+ body.each { |bit| bits << bit }
+ assert_equal ['hi mom'], bits
+ end
+
+ def test_connections_are_cleared_after_body_close
+ _, _, body = @management.call(@env)
+ body.close
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_active_connections_are_not_cleared_on_body_close_during_test
+ @env['rack.test'] = true
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_closed_if_exception
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_not_closed_if_exception_and_test
+ @env['rack.test'] = true
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
- test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert !@connections_cleared
+ test "doesn't clear active connections when running in a test case" do
+ @env['rack.test'] = true
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+ end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7ac14fa8d6..f92f4e62c5 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -18,6 +18,14 @@ module ActiveRecord
end
end
+ def test_active_connection?
+ assert !@pool.active_connection?
+ assert @pool.connection
+ assert @pool.active_connection?
+ @pool.release_connection
+ assert !@pool.active_connection?
+ end
+
def test_pool_caches_columns
columns = @pool.columns['posts']
assert_equal columns, @pool.columns['posts']
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a6738fb654..b1ce846218 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -413,7 +413,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates(Topic) do
Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
topic = Topic.select('id, author_name').first
- topic.update_attribute :author_name, 'John'
+ topic.update_column :author_name, 'John'
topic = Topic.first
assert_not_nil topic.content
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 543981b4a0..014588b6d9 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -74,6 +74,11 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_exists_does_not_instantiate_records
+ Developer.expects(:instantiate).never
+ Developer.exists?
+ end
+
def test_find_by_array_of_one_id
assert_kind_of(Array, Topic.find([ 1 ]))
assert_equal(1, Topic.find([ 1 ]).length)
@@ -203,6 +208,14 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_model_class_responds_to_first_bang
+ assert_equal topics(:first), Topic.order(:id).first!
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.delete_all
+ Topic.first!
+ end
+ end
+
def test_last_bang_present
assert_nothing_raised do
assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
@@ -215,6 +228,14 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_model_class_responds_to_last_bang
+ assert_equal topics(:fourth), Topic.last!
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.delete_all
+ Topic.last!
+ end
+ end
+
def test_unexisting_record_exception_handling
assert_raise(ActiveRecord::RecordNotFound) {
Topic.find(1).parent
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index cbaaca764b..8ebde933b4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -22,6 +22,33 @@ class LogSubscriberTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = logger
end
+ def test_schema_statements_are_ignored
+ event = Struct.new(:duration, :payload)
+
+ logger = Class.new(ActiveRecord::LogSubscriber) {
+ attr_accessor :debugs
+
+ def initialize
+ @debugs = []
+ super
+ end
+
+ def debug message
+ @debugs << message
+ end
+ }.new
+ assert_equal 0, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!' }))
+ assert_equal 1, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'foo' }))
+ assert_equal 2, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'SCHEMA' }))
+ assert_equal 2, logger.debugs.length
+ end
+
def test_basic_query_logging
Developer.all
wait
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index fb050c3e52..9b20ea08de 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -64,7 +64,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Reply.scopes.include?(:base)
assert_equal Reply.find(:all), Reply.base
end
-
+
def test_classes_dont_inherit_subclasses_scopes
assert !ActiveRecord::Base.scopes.include?(:base)
end
@@ -246,6 +246,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_no_queries { assert topics.any? }
end
+ def test_model_class_should_respond_to_any
+ assert Topic.any?
+ Topic.delete_all
+ assert !Topic.any?
+ end
+
def test_many_should_not_load_results
topics = Topic.base
assert_queries(2) do
@@ -280,6 +286,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
+ def test_model_class_should_respond_to_many
+ Topic.delete_all
+ assert !Topic.many?
+ Topic.create!
+ assert !Topic.many?
+ Topic.create!
+ assert Topic.many?
+ end
+
def test_should_build_on_top_of_scope
topic = Topic.approved.build({})
assert topic.approved
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8ca9d626d1..9aa13f04cd 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -389,6 +389,92 @@ class PersistencesTest < ActiveRecord::TestCase
assert_not_equal prev_month, developer.updated_at
end
+ def test_update_column
+ topic = Topic.find(1)
+ topic.update_column("approved", true)
+ assert topic.approved?
+ topic.reload
+ assert topic.approved?
+
+ topic.update_column(:approved, false)
+ assert !topic.approved?
+ topic.reload
+ assert !topic.approved?
+ end
+
+ def test_update_column_should_not_use_setter_method
+ dev = Developer.find(1)
+ dev.instance_eval { def salary=(value); write_attribute(:salary, value * 2); end }
+
+ dev.update_column(:salary, 80000)
+ assert_equal 80000, dev.salary
+
+ dev.reload
+ assert_equal 80000, dev.salary
+ end
+
+ def test_update_column_should_raise_exception_if_new_record
+ topic = Topic.new
+ assert_raises(ActiveRecord::ActiveRecordError) { topic.update_column("approved", false) }
+ end
+
+ def test_update_column_should_not_leave_the_object_dirty
+ topic = Topic.find(1)
+ topic.update_attribute("content", "Have a nice day")
+
+ topic.reload
+ topic.update_column(:content, "You too")
+ assert_equal [], topic.changed
+
+ topic.reload
+ topic.update_column("content", "Have a nice day")
+ assert_equal [], topic.changed
+ end
+
+ def test_update_column_with_model_having_primary_key_other_than_id
+ minivan = Minivan.find('m1')
+ new_name = 'sebavan'
+
+ minivan.update_column(:name, new_name)
+ assert_equal new_name, minivan.name
+ end
+
+ def test_update_column_for_readonly_attribute
+ minivan = Minivan.find('m1')
+ prev_color = minivan.color
+ assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, 'black') }
+ assert_equal prev_color, minivan.color
+ end
+
+ def test_update_column_should_not_modify_updated_at
+ developer = Developer.find(1)
+ prev_month = Time.now.prev_month
+
+ developer.update_column(:updated_at, prev_month)
+ assert_equal prev_month, developer.updated_at
+
+ developer.update_column(:salary, 80001)
+ assert_equal prev_month, developer.updated_at
+
+ developer.reload
+ assert_equal prev_month.to_i, developer.updated_at.to_i
+ end
+
+ def test_update_column_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_column(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+
+ t.reload
+ assert_equal author_name, t.author_name
+ assert_equal 'super_title', t.title
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 63d8c7d1c1..05a41d8a0a 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -136,4 +136,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
end
end
+
+ def test_quoted_primary_key_after_set_primary_key
+ k = Class.new( ActiveRecord::Base )
+ assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key
+ k.primary_key = "foo"
+ assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
+ k.set_primary_key "bar"
+ assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key
+ end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 7369aaea1d..30a783d5a2 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -429,9 +429,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_except_and_order_overrides_default_scope_order
+ def test_reorder_overrides_default_scope_order
expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.except(:order).order('name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index dfb5735b5d..fc9df8c7a3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -151,6 +151,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fourth).title, topics.first.title
end
+ def test_finding_with_reorder
+ topics = Topic.order('author_name').order('title').reorder('id').all
+ topics_titles = topics.map{ |t| t.title }
+ assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles
+ end
+
def test_finding_with_order_and_take
entrants = Entrant.order("id ASC").limit(2).to_a
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 1c21f0f3b6..ceb1452afd 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -131,8 +131,9 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
pet = toy.pet
owner = pet.owner
+ time = 3.days.ago
- owner.update_attribute(:updated_at, (time = 3.days.ago))
+ owner.update_column(:updated_at, time)
toy.touch
owner.reload