# frozen_string_literal: true
require "cases/helper"
require "models/author"
require "models/category"
require "models/comment"
require "models/computer"
require "models/developer"
require "models/post"
require "models/person"
require "models/pet"
require "models/toy"
require "models/topic"
require "models/tag"
require "models/tagging"
require "models/warehouse_thing"
class UpdateAllTest < ActiveRecord::TestCase
fixtures :authors, :author_addresses, :comments, :developers, :posts, :people, :pets, :toys, :tags, :taggings, "warehouse-things"
class TopicWithCallbacks < ActiveRecord::Base
self.table_name = :topics
cattr_accessor :topic_count
before_update { |topic| topic.author_name = "David" if topic.author_name.blank? }
after_update { |topic| topic.class.topic_count = topic.class.count }
end
def test_update_all_with_scope
tag = Tag.first
Post.tagged_with(tag.id).update_all(title: "rofl")
posts = Post.tagged_with(tag.id).all.to_a
assert_operator posts.length, :>, 0
posts.each { |post| assert_equal "rofl", post.title }
end
def test_update_all_with_non_standard_table_name
assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
assert_equal 0, WarehouseThing.find(1).value
end
def test_update_all_with_blank_argument
assert_raises(ArgumentError) { Comment.update_all({}) }
end
def test_update_all_with_joins
pets = Pet.joins(:toys).where(toys: { name: "Bone" })
assert_equal true, pets.exists?
assert_equal pets.count, pets.update_all(name: "Bob")
end
def test_update_all_with_left_joins
pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
assert_equal true, pets.exists?
assert_equal pets.count, pets.update_all(name: "Bob")
end
def test_update_all_with_includes
pets = Pet.includes(:toys).where(toys: { name: "Bone" })
assert_equal true, pets.exists?
assert_equal pets.count, pets.update_all(name: "Bob")
end
def test_update_all_with_joins_and_limit
comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).limit(1)
assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
assert_equal posts(:thinking), comments(:greetings).post
end
def test_update_all_with_joins_and_limit_and_order
comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("comments.id").limit(1)
assert_equal 1, comments.update_all(post_id: posts(:thinking).id)
assert_equal posts(:thinking), comments(:greetings).post
assert_equal posts(:welcome), comments(:more_greetings).post
end
def test_update_all_with_joins_and_offset
all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
count = all_comments.count
comments = all_comments.offset(1)
assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
end
def test_update_all_with_joins_and_offset_and_order
all_comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id).order("posts.id", "comments.id")
count = all_comments.count
comments = all_comments.offset(1)
assert_equal count - 1, comments.update_all(post_id: posts(:thinking).id)
assert_equal posts(:thinking), comments(:more_greetings).post
assert_equal posts(:welcome), comments(:greetings).post
end
def test_update_counters_with_joins
assert_nil pets(:parrot).integer
Pet.joins(:toys).where(toys: { name: "Bone" }).update_counters(integer: 1)
assert_equal 1, pets(:parrot).reload.integer
end
def test_touch_all_updates_records_timestamps
david = developers(:david)
david_previously_updated_at = david.updated_at
jamis = developers(:jamis)
jamis_previously_updated_at = jamis.updated_at
Developer.where(name: "David").touch_all
assert_not_equal david_previously_updated_at, david.reload.updated_at
assert_equal jamis_previously_updated_at, jamis.reload.updated_at
end
def test_touch_all_with_custom_timestamp
developer = developers(:david)
previously_created_at = developer.created_at
previously_updated_at = developer.updated_at
Developer.where(name: "David").touch_all(:created_at)
developer.reload
assert_not_equal previously_created_at, developer.created_at
assert_not_equal previously_updated_at, developer.updated_at
end
def test_touch_all_with_given_time
developer = developers(:david)
previously_created_at = developer.created_at
previously_updated_at = developer.updated_at
new_time = Time.utc(2015, 2, 16, 4, 54, 0)
Developer.where(name: "David").touch_all(:created_at, time: new_time)
developer.reload
assert_not_equal previously_created_at, developer.created_at
assert_not_equal previously_updated_at, developer.updated_at
assert_equal new_time, developer.created_at
assert_equal new_time, developer.updated_at
end
def test_update_on_relation
topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
topics.update(title: "adequaterecord")
assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
assert_equal "adequaterecord", topic1.reload.title
assert_equal "adequaterecord", topic2.reload.title
# Testing that the before_update callbacks have run
assert_equal "David", topic1.reload.author_name
assert_equal "David", topic2.reload.author_name
end
def test_update_with_ids_on_relation
topic1 = TopicWithCallbacks.create!(title: "arel", author_name: nil)
topic2 = TopicWithCallbacks.create!(title: "activerecord", author_name: nil)
topics = TopicWithCallbacks.none
topics.update(
[topic1.id, topic2.id],
[{ title: "adequaterecord" }, { title: "adequaterecord" }]
)
assert_equal TopicWithCallbacks.count, TopicWithCallbacks.topic_count
assert_equal "adequaterecord", topic1.reload.title
assert_equal "adequaterecord", topic2.reload.title
# Testing that the before_update callbacks have run
assert_equal "David", topic1.reload.author_name
assert_equal "David", topic2.reload.author_name
end
def test_update_on_relation_passing_active_record_object_is_not_permitted
topic = Topic.create!(title: "Foo", author_name: nil)
assert_raises(ArgumentError) do
Topic.where(id: topic.id).update(topic, title: "Bar")
end
end
def test_update_all_cares_about_optimistic_locking
david = people(:david)
travel 5.seconds do
now = Time.now.utc
assert_not_equal now, david.updated_at
people = Person.where(id: people(:michael, :david, :susan))
expected = people.pluck(:lock_version)
expected.map! { |version| version + 1 }
people.update_all(updated_at: now)
assert_equal [now] * 3, people.pluck(:updated_at)
assert_equal expected, people.pluck(:lock_version)
assert_raises(ActiveRecord::StaleObjectError) do
david.touch(time: now)
end
end
end
def test_update_counters_cares_about_optimistic_locking
david = people(:david)
travel 5.seconds do
now = Time.now.utc
assert_not_equal now, david.updated_at
people = Person.where(id: people(:michael, :david, :susan))
expected = people.pluck(:lock_version)
expected.map! { |version| version + 1 }
people.update_counters(touch: [time: now])
assert_equal [now] * 3, people.pluck(:updated_at)
assert_equal expected, people.pluck(:lock_version)
assert_raises(ActiveRecord::StaleObjectError) do
david.touch(time: now)
end
end
end
def test_touch_all_cares_about_optimistic_locking
david = people(:david)
travel 5.seconds do
now = Time.now.utc
assert_not_equal now, david.updated_at
people = Person.where(id: people(:michael, :david, :susan))
expected = people.pluck(:lock_version)
expected.map! { |version| version + 1 }
people.touch_all(time: now)
assert_equal [now] * 3, people.pluck(:updated_at)
assert_equal expected, people.pluck(:lock_version)
assert_raises(ActiveRecord::StaleObjectError) do
david.touch(time: now)
end
end
end
def test_klass_level_update_all
travel 5.seconds do
now = Time.now.utc
Person.all.each do |person|
assert_not_equal now, person.updated_at
end
Person.update_all(updated_at: now)
Person.all.each do |person|
assert_equal now, person.updated_at
end
end
end
def test_klass_level_touch_all
travel 5.seconds do
now = Time.now.utc
Person.all.each do |person|
assert_not_equal now, person.updated_at
end
Person.touch_all(time: now)
Person.all.each do |person|
assert_equal now, person.updated_at
end
end
end
# Oracle UPDATE does not support ORDER BY
unless current_adapter?(:OracleAdapter)
def test_update_all_ignores_order_without_limit_from_association
author = authors(:david)
assert_nothing_raised do
assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
end
end
def test_update_all_doesnt_ignore_order
assert_equal authors(:david).id + 1, authors(:mary).id # make sure there is going to be a duplicate PK error
test_update_with_order_succeeds = lambda do |order|
Author.order(order).update_all("id = id + 1")
rescue ActiveRecord::ActiveRecordError
false
end
if test_update_with_order_succeeds.call("id DESC")
# test that this wasn't a fluke and using an incorrect order results in an exception
assert_not test_update_with_order_succeeds.call("id ASC")
else
# test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
test_update_with_order_succeeds.call("id DESC")
end
end
end
def test_update_all_with_order_and_limit_updates_subset_only
author = authors(:david)
limited_posts = author.posts_sorted_by_id_limited
assert_equal 1, limited_posts.size
assert_equal 2, limited_posts.limit(2).size
assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
assert_equal "bulk update!", posts(:welcome).body
assert_not_equal "bulk update!", posts(:thinking).body
end
def test_update_all_with_order_and_limit_and_offset_updates_subset_only
author = authors(:david)
limited_posts = author.posts_sorted_by_id_limited.offset(1)
assert_equal 1, limited_posts.size
assert_equal 2, limited_posts.limit(2).size
assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
assert_equal "bulk update!", posts(:thinking).body
assert_not_equal "bulk update!", posts(:welcome).body
end
end
end