aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorSebastian Martinez <sebastian@wyeworks.com>2011-03-27 19:12:28 -0300
committerSantiago Pastorino <santiago@wyeworks.com>2011-03-27 21:47:38 -0300
commit245542ea2994961731be105db6c076256a22a7a9 (patch)
tree4780488d15d639f516d14b976bb5e077c6dbc3d9 /activerecord
parente6a8a3adaf08cc9d551b4a125aaa276812984be3 (diff)
downloadrails-245542ea2994961731be105db6c076256a22a7a9.tar.gz
rails-245542ea2994961731be105db6c076256a22a7a9.tar.bz2
rails-245542ea2994961731be105db6c076256a22a7a9.zip
Added new #update_column method.
Signed-off-by: Santiago Pastorino <santiago@wyeworks.com>
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG9
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb1
-rw-r--r--activerecord/lib/active_record/persistence.rb14
-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/base_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/dirty_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb86
-rw-r--r--activerecord/test/cases/timestamp_test.rb3
14 files changed, 132 insertions, 21 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 6be46349fb..3b6a7d7208 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,14 @@
*Rails 3.1.0 (unreleased)*
+* 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/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/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/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/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/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index caf07a7357..c97f1ae634 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -311,8 +311,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/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/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8ca9d626d1..8e6141eb81 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, developer.updated_at
+ 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/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