aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/relation.rb16
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb4
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb6
-rw-r--r--activerecord/test/cases/persistence_test.rb167
-rw-r--r--activerecord/test/cases/relation/delete_all_test.rb104
-rw-r--r--activerecord/test/cases/relation/update_all_test.rb239
-rw-r--r--activerecord/test/cases/relations_test.rb180
-rw-r--r--ci/qunit-selenium-runner.rb2
-rw-r--r--railties/test/application/configuration_test.rb18
14 files changed, 407 insertions, 359 deletions
diff --git a/Gemfile b/Gemfile
index 1b0224dfdf..5bf443b7c8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -121,7 +121,7 @@ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
gem "sqlite3", "~> 1.3.6"
group :db do
- gem "pg", ">= 0.18.0", "< 1.1"
+ gem "pg", ">= 0.18.0"
gem "mysql2", ">= 0.4.10"
end
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 52cde3841a..6863f718c4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -181,7 +181,7 @@ GEM
xpath (~> 3.1)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
- chromedriver-helper (1.2.0)
+ chromedriver-helper (2.0.0)
archive-zip (~> 0.10)
nokogiri (~> 1.8)
coffee-rails (4.2.2)
@@ -302,7 +302,7 @@ GEM
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
- marcel (0.3.2)
+ marcel (0.3.3)
mimemagic (~> 0.3.2)
memoist (0.16.0)
method_source (0.9.0)
@@ -345,9 +345,9 @@ GEM
parser (2.5.1.2)
ast (~> 2.4.0)
path_expander (1.0.3)
- pg (1.0.0)
- pg (1.0.0-x64-mingw32)
- pg (1.0.0-x86-mingw32)
+ pg (1.1.3)
+ pg (1.1.3-x64-mingw32)
+ pg (1.1.3-x86-mingw32)
powerpack (0.1.2)
psych (3.0.2)
public_suffix (3.0.3)
@@ -533,7 +533,7 @@ DEPENDENCIES
minitest-bisect
mysql2 (>= 0.4.10)
nokogiri (>= 1.8.1)
- pg (>= 0.18.0, < 1.1)
+ pg (>= 0.18.0)
psych (~> 3.0)
puma
que
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index ad148efcfe..1305216be2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -71,6 +71,11 @@ module ActiveRecord
256
end
deprecate :joins_per_query
+
+ private
+ def bind_params_length
+ 65535
+ end
end
end
end
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 fdc9ffa688..5b24a467ec 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -46,11 +46,16 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [], preparable: nil)
arel = arel_from_relation(arel)
sql, binds = to_sql_and_binds(arel, binds)
+
if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
+ elsif binds.length > bind_params_length
+ sql, binds = unprepared_statement { to_sql_and_binds(arel) }
+ preparable = false
else
preparable = visitor.preparable
end
+
if prepared_statements && preparable
select_prepared(sql, name, binds)
else
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 86e5d14c81..806f8a1cbb 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -349,7 +349,7 @@ module ActiveRecord
stmt = Arel::UpdateManager.new
- stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
+ stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates, table.name))
stmt.table(table)
if has_join_values? || offset_value
@@ -377,14 +377,14 @@ module ActiveRecord
updates = counters.map do |counter_name, value|
operator = value < 0 ? "-" : "+"
- quoted_column = connection.quote_column_name(counter_name)
+ quoted_column = connection.quote_table_name_for_assignment(table.name, counter_name)
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
end
if touch
names = touch if touch != true
touch_updates = klass.touch_attributes_with_time(*names)
- updates << klass.sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
+ updates << klass.sanitize_sql_for_assignment(touch_updates, table.name) unless touch_updates.empty?
end
update_all updates.join(", ")
@@ -414,14 +414,12 @@ module ActiveRecord
# Person.where(name: 'David').touch_all
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
def touch_all(*names, time: nil)
- updates = touch_attributes_with_time(*names, time: time)
-
if klass.locking_enabled?
- quoted_locking_column = connection.quote_column_name(klass.locking_column)
- updates = sanitize_sql_for_assignment(updates) + ", #{quoted_locking_column} = COALESCE(#{quoted_locking_column}, 0) + 1"
+ names << { time: time }
+ update_counters(klass.locking_column => 1, touch: names)
+ else
+ update_all klass.touch_attributes_with_time(*names, time: time)
end
-
- update_all(updates)
end
# Destroys the records by instantiating each
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index cb70b8bcde..f734cd0ad8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -57,14 +57,10 @@ module ActiveRecord
end
def build_bind_attribute(column_name, value)
- attr = build_query_attribute(column_name, value)
+ attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
Arel::Nodes::BindParam.new(attr)
end
- def build_query_attribute(column_name, value)
- Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
- end
-
protected
def expand_from_hash(attributes)
return ["1=0"] if attributes.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 0895b9fba6..fadb3c420d 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -22,8 +22,8 @@ module ActiveRecord
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
- attr = predicate_builder.build_query_attribute(attribute.name, v)
- attr.value_for_database if attr.boundable?
+ bind = predicate_builder.build_bind_attribute(attribute.name, v)
+ bind if bind.value.boundable?
end.compact!
values.empty? ? NullPredicate : attribute.in(values)
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 91cc49385c..9c1f7aaef2 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -34,6 +34,12 @@ if ActiveRecord::Base.connection.prepared_statements
ActiveSupport::Notifications.unsubscribe(@subscription)
end
+ def test_too_many_binds
+ bind_params_length = @connection.send(:bind_params_length)
+ topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal Topic.count, topics.count
+ end
+
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 7348a22dd3..576fd21642 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -13,90 +13,15 @@ require "models/developer"
require "models/computer"
require "models/project"
require "models/minimalistic"
-require "models/warehouse_thing"
require "models/parrot"
require "models/minivan"
-require "models/owner"
require "models/person"
-require "models/pet"
require "models/ship"
-require "models/toy"
require "models/admin"
require "models/admin/user"
-require "rexml/document"
class PersistenceTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts, :minivans, :pets, :toys
-
- # 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|
- begin
- Author.order(order).update_all("id = id + 1")
- rescue ActiveRecord::ActiveRecordError
- false
- end
- end
-
- if test_update_with_order_succeeds.call("id DESC")
- assert_not test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
- 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
-
- def test_delete_all_with_order_and_limit_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
- assert posts(:thinking)
- end
-
- def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
- author = authors(:david)
- limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
- assert_equal 1, limited_posts.size
- assert_equal 2, limited_posts.limit(2).size
- assert_equal 1, limited_posts.delete_all
- assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
- assert posts(:welcome)
- end
- end
+ fixtures :topics, :companies, :developers, :accounts, :minimalistics, :authors, :author_addresses, :posts, :minivans
def test_update_many
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
@@ -145,34 +70,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal Topic.count, Topic.delete_all
end
- def test_delete_all_with_joins_and_where_part_is_hash
- pets = Pet.joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_joins_and_where_part_is_not_hash
- pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_left_joins
- pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
- def test_delete_all_with_includes
- pets = Pet.includes(:toys).where(toys: { name: "Bone" })
-
- assert_equal true, pets.exists?
- assert_equal pets.count, pets.delete_all
- end
-
def test_increment_attribute
assert_equal 50, accounts(:signals37).credit_limit
accounts(:signals37).increment! :credit_limit
@@ -230,18 +127,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_operator previously_written_on, :<, topic.written_on
end
- def test_destroy_all
- conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
- assert_not_empty topics_by_mary
-
- assert_difference("Topic.count", -topics_by_mary.size) do
- destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)
- assert_equal topics_by_mary, destroyed
- assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"
- end
- end
-
def test_destroy_many
clients = Client.find([2, 3])
@@ -638,32 +523,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nil Topic.find(2).last_read
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_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_delete_new_record
client = Client.new(name: "37signals")
client.delete
@@ -1185,21 +1044,19 @@ class PersistenceTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.disable_query_cache!
end
- class SaveTest < ActiveRecord::TestCase
- def test_save_touch_false
- pet = Pet.create!(
- name: "Bob",
- created_at: 1.day.ago,
- updated_at: 1.day.ago)
+ def test_save_touch_false
+ parrot = Parrot.create!(
+ name: "Bob",
+ created_at: 1.day.ago,
+ updated_at: 1.day.ago)
- created_at = pet.created_at
- updated_at = pet.updated_at
+ created_at = parrot.created_at
+ updated_at = parrot.updated_at
- pet.name = "Barb"
- pet.save!(touch: false)
- assert_equal pet.created_at, created_at
- assert_equal pet.updated_at, updated_at
- end
+ parrot.name = "Barb"
+ parrot.save!(touch: false)
+ assert_equal parrot.created_at, created_at
+ assert_equal parrot.updated_at, updated_at
end
def test_reset_column_information_resets_children
diff --git a/activerecord/test/cases/relation/delete_all_test.rb b/activerecord/test/cases/relation/delete_all_test.rb
new file mode 100644
index 0000000000..446d7621ea
--- /dev/null
+++ b/activerecord/test/cases/relation/delete_all_test.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/author"
+require "models/post"
+require "models/pet"
+require "models/toy"
+
+class DeleteAllTest < ActiveRecord::TestCase
+ fixtures :authors, :author_addresses, :posts, :pets, :toys
+
+ def test_destroy_all
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) do
+ destroyed = davids.destroy_all
+ assert_equal [authors(:david)], destroyed
+ assert_predicate destroyed.first, :frozen?
+ end
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all
+ davids = Author.where(name: "David")
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+ assert_not_predicate davids, :loaded?
+ end
+
+ def test_delete_all_loaded
+ davids = Author.where(name: "David")
+
+ # Force load
+ assert_equal [authors(:david)], davids.to_a
+ assert_predicate davids, :loaded?
+
+ assert_difference("Author.count", -1) { davids.delete_all }
+
+ assert_equal [], davids.to_a
+ assert_predicate davids, :loaded?
+ end
+
+ def test_delete_all_with_unpermitted_relation_raises_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_hash
+ pets = Pet.joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ pets = Pet.joins(:toys).where("toys.name = ?", "Bone")
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_left_joins
+ pets = Pet.left_joins(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ def test_delete_all_with_includes
+ pets = Pet.includes(:toys).where(toys: { name: "Bone" })
+
+ assert_equal true, pets.exists?
+ assert_equal pets.count, pets.delete_all
+ end
+
+ unless current_adapter?(:OracleAdapter)
+ def test_delete_all_with_order_and_limit_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
+ assert posts(:thinking)
+ end
+
+ def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
+ assert posts(:welcome)
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/update_all_test.rb b/activerecord/test/cases/relation/update_all_test.rb
new file mode 100644
index 0000000000..09c365f31b
--- /dev/null
+++ b/activerecord/test/cases/relation/update_all_test.rb
@@ -0,0 +1,239 @@
+# 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_touch_all_updates_locking_column
+ person = people(:david)
+
+ assert_difference -> { person.reload.lock_version }, +1 do
+ Person.where(first_name: "David").touch_all
+ end
+ 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
+
+ # 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|
+ begin
+ Author.order(order).update_all("id = id + 1")
+ rescue ActiveRecord::ActiveRecordError
+ false
+ end
+ 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
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index c14dc23cf3..9914a61033 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -29,13 +29,6 @@ require "models/subscriber"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :author_addresses, :topics, :entrants, :developers, :people, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
- 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_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -863,45 +856,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:bob), authors.last
end
- def test_destroy_all
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.destroy_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all
- davids = Author.where(name: "David")
-
- assert_difference("Author.count", -1) { davids.delete_all }
- assert_not_predicate davids, :loaded?
- end
-
- def test_delete_all_loaded
- davids = Author.where(name: "David")
-
- # Force load
- assert_equal [authors(:david)], davids.to_a
- assert_predicate davids, :loaded?
-
- assert_difference("Author.count", -1) { davids.delete_all }
-
- assert_equal [], davids.to_a
- assert_predicate davids, :loaded?
- end
-
- def test_delete_all_with_unpermitted_relation_raises_error
- assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
- end
-
def test_select_with_aggregates
posts = Post.select(:title, :body)
@@ -986,14 +940,6 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(1) { assert_equal 11, posts.load.size }
end
- def test_update_all_with_scope
- tag = Tag.first
- Post.tagged_with(tag.id).update_all title: "rofl"
- list = Post.tagged_with(tag.id).all.to_a
- assert_operator list.length, :>, 0
- list.each { |post| assert_equal "rofl", post.title }
- end
-
def test_count_explicit_columns
Post.update_all(comments_count: nil)
posts = Post.all
@@ -1497,132 +1443,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal authors(:david), Author.order("id DESC , name DESC").last
end
- def test_update_all_with_blank_argument
- assert_raises(ArgumentError) { Comment.update_all({}) }
- end
-
- def test_update_all_with_joins
- comments = Comment.joins(:post).where("posts.id" => posts(:welcome).id)
- count = comments.count
-
- assert_equal count, comments.update_all(post_id: posts(:thinking).id)
- assert_equal posts(:thinking), comments(:greetings).post
- 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)
- 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_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 = 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 = 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_touch_all_updates_locking_column
- person = people(:david)
-
- assert_difference -> { person.reload.lock_version }, +1 do
- Person.where(first_name: "David").touch_all
- end
- 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_distinct
tag1 = Tag.create(name: "Foo")
tag2 = Tag.create(name: "Foo")
diff --git a/ci/qunit-selenium-runner.rb b/ci/qunit-selenium-runner.rb
index 05bcab8cdb..132b3d17eb 100644
--- a/ci/qunit-selenium-runner.rb
+++ b/ci/qunit-selenium-runner.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "qunit/selenium/test_runner"
-require "chromedriver/helper"
+require "chromedriver-helper"
driver_options = Selenium::WebDriver::Chrome::Options.new
driver_options.add_argument("--headless")
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 44f6af5d4a..11cd6dd1e6 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -2053,6 +2053,24 @@ module ApplicationTests
assert_equal [ "password", "credit_card_number" ].to_set, ActiveRecord::Base.filter_attributes
end
+ test "ActiveStorage.routes_prefix can be configured via config.active_storage.routes_prefix" do
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ config.active_storage.routes_prefix = '/files'
+ end
+ RUBY
+
+ output = rails("routes", "-g", "active_storage")
+ assert_equal <<~MESSAGE, output
+ Prefix Verb URI Pattern Controller#Action
+ rails_service_blob GET /files/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
+ rails_blob_representation GET /files/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
+ rails_disk_service GET /files/disk/:encoded_key/*filename(.:format) active_storage/disk#show
+ update_rails_disk_service PUT /files/disk/:encoded_token(.:format) active_storage/disk#update
+ rails_direct_uploads POST /files/direct_uploads(.:format) active_storage/direct_uploads#create
+ MESSAGE
+ end
+
private
def force_lazy_load_hooks
yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it.