aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb32
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb9
-rw-r--r--activerecord/test/cases/connection_pool_test.rb13
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb19
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb10
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb46
-rw-r--r--activerecord/test/cases/relation/merging_test.rb2
-rw-r--r--activerecord/test/cases/relation/or_test.rb25
-rw-r--r--activerecord/test/models/comment.rb1
-rw-r--r--activerecord/test/schema/schema.rb3
20 files changed, 151 insertions, 83 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index ee2e79889f..5c45187d8f 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -276,7 +276,7 @@ module ActiveRecord
end
# Returns true if statement cache should be skipped on the association reader.
- def skip_statement_cache?
+ def skip_statement_cache?(scope)
reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index edc53e2517..0cb17b47e8 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -300,7 +300,8 @@ module ActiveRecord
private
def find_target
- return scope.to_a if skip_statement_cache?
+ scope = self.scope
+ return scope.to_a if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 91580a28d0..2b426da607 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -36,7 +36,8 @@ module ActiveRecord
end
def find_target
- return scope.take if skip_statement_cache?
+ scope = self.scope
+ return scope.take if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
@@ -63,6 +64,10 @@ module ActiveRecord
end
def _create_record(attributes, raise_error = false)
+ unless owner.persisted?
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
+ end
+
record = build_record(attributes)
yield(record) if block_given?
saved = record.save
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 c6811a4802..56f0d6d1f4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -308,16 +308,23 @@ module ActiveRecord
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
- key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = binds.map(&:value_for_database).map do |value|
+
+ table = Arel::Table.new(table_name)
+
+ values = binds.map do |bind|
+ value = bind.value_for_database
begin
quote(value)
rescue TypeError
- quote(YAML.dump(value))
+ value = YAML.dump(value)
end
+ [table[bind.name], value]
end
- execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert"
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ manager.insert(values)
+ execute manager.to_sql, "Fixture Insert"
end
def empty_insert_statement_value
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index a4fecc4a8e..93f4529202 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -55,7 +55,7 @@ module ActiveRecord
create_sql << "(#{statements.join(', ')})" if statements.present?
add_table_options!(create_sql, table_options(o))
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
+ create_sql << " AS #{to_sql(o.as)}" if o.as
create_sql
end
@@ -114,6 +114,11 @@ module ActiveRecord
sql
end
+ def to_sql(sql)
+ sql = sql.to_sql if sql.respond_to?(:to_sql)
+ sql
+ end
+
def foreign_key_in_create(from_table, to_table, options)
options = foreign_key_options(from_table, to_table, options)
accept ForeignKeyDefinition.new(from_table, to_table, options)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index 8c67a7a80b..9f1021456b 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -13,15 +13,6 @@ module ActiveRecord
result
end
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- def select_rows(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- @connection.next_result while @connection.more_results?
- result.to_a
- end
- end
-
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
@@ -58,16 +49,6 @@ module ActiveRecord
@connection.last_id
end
- def select_result(arel, name, binds)
- arel, binds = binds_from_relation(arel, binds)
- sql = to_sql(arel, binds)
- if without_prepared_statement?(binds)
- execute_and_free(sql, name) { |result| yield result }
- else
- exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
- end
- end
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index f9e1e046ea..fc57e41fc9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -45,6 +45,13 @@ module ActiveRecord
indexes
end
+ def remove_column(table_name, column_name, type = nil, options = {})
+ if foreign_key_exists?(table_name, column: column_name)
+ remove_foreign_key(table_name, column: column_name)
+ end
+ super
+ end
+
def internal_string_options_for_primary_key
super.tap do |options|
if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 705e6063dc..ac5efbebeb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -7,30 +7,6 @@ module ActiveRecord
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
- def select_value(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
- end
- end
-
- def select_values(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- if result.nfields > 0
- result.column_values(0)
- else
- []
- end
- end
- end
-
- # Executes a SELECT query and returns an array of rows. Each row is an
- # array of field values.
- def select_rows(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- result.values
- end
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -175,14 +151,6 @@ module ActiveRecord
def suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
-
- def select_result(arel, name, binds)
- arel, binds = binds_from_relation(arel, binds)
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
- yield result
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 79e65baae5..6ccdd7adcb 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1167,7 +1167,7 @@ module ActiveRecord
end
end
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having]
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope]
def structurally_incompatible_values_for_or(other)
STRUCTURAL_OR_METHODS.reject do |method|
get_value(method) == other.get_value(method)
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 7c11d2e7fc..bf3b8dcd63 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -307,6 +307,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_create_when_parent_is_new_raises
+ firm = Firm.new
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
+ firm.create_account
+ end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
+ end
+
def test_reload_association
odegy = companies(:odegy)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7e88c9cf7a..00a0187b57 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -499,21 +499,8 @@ module ActiveRecord
if failed
second_thread_done.set
- puts
- puts ">>> test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads / #{group_action_method}"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
-
first_thread.join(2)
second_thread.join(2)
-
- puts "---"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
- puts "<<<"
- puts
end
first_thread.join(10) || raise("first_thread got stuck")
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 5aa41b9d6d..9a1c1c3f3f 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -169,6 +169,25 @@ module JSONSharedTestCases
assert_not json.changed?
end
+ def test_changes_in_place_ignores_key_order
+ json = klass.new
+ assert_not json.changed?
+
+ json.payload = { "three" => "four", "one" => "two" }
+ json.save!
+ json.reload
+
+ json.payload = { "three" => "four", "one" => "two" }
+ assert_not json.changed?
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ json.save!
+ json.reload
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ assert_not json.changed?
+ end
+
def test_changes_in_place_with_ruby_object
time = Time.now.utc
json = klass.create!(payload: time)
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index f1ddac1ee2..718b9a0613 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -139,6 +139,16 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ test "removing column removes foreign key" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_column :testings, :testing_parent_id
+ end
+ end
+
test "foreign key methods respect pluralize_table_names" do
begin
original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 57f94950f9..3a49a41580 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -502,11 +502,10 @@ class MigrationTest < ActiveRecord::TestCase
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM people WHERE id = 1"
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -514,11 +513,10 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_query_from_relation
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id).where(id: 1)
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 5a62cbd3a6..154faa56aa 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -117,7 +117,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
- pirate = Pirate.new(catchphrase: "Stop wastin' me time")
+ pirate = Pirate.create(catchphrase: "Stop wastin' me time")
ship = pirate.create_ship(name: "s1")
pirate.update(ship_attributes: { name: "s2", id: ship.id })
assert_equal "s1", ship.reload.name
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 494663eb04..9b741545d7 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -204,6 +204,52 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_exists_queries_with_cache
+ Post.cache do
+ assert_queries(1) { Post.exists?; Post.exists? }
+ end
+ end
+
+ def test_select_all_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_all(Post.all) }
+ end
+ end
+ end
+
+ def test_select_one_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_one(Post.all) }
+ end
+ end
+ end
+
+ def test_select_value_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_value(Post.all) }
+ end
+ end
+ end
+
+ def test_select_values_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_values(Post.all) }
+ end
+ end
+ end
+
+ def test_select_rows_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_rows(Post.all) }
+ end
+ end
+ end
+
def test_query_cache_dups_results_correctly
Task.cache do
now = Time.now.utc
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index c3b39a9295..3901824aac 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -143,7 +143,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase
assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name
end
- test "relation merging (using a proc argument)" do
+ test "relation merging (using a proc argument)" do
dev = Developer.where(name: "Jamis").first
comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first)
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index abb7ca72dd..61b6601580 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -59,6 +59,31 @@ module ActiveRecord
assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
+ def test_or_with_unscope_where
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where("id = 1 and id != 2")
+ assert_equal expected, partial.or(partial.unscope(:where).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_where_column
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where(id: 1).where.not(id: 2)
+ assert_equal expected, partial.or(partial.unscope(where: :id).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_order
+ expected = Post.where("id = 1 or id = 2")
+ assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a
+ end
+
+ def test_or_with_incompatible_unscope
+ error = assert_raises ArgumentError do
+ Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a
+ end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
+ end
+
def test_or_when_grouping
groups = Post.where("id < 10").group("body").select("body, COUNT(*) AS c")
expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] }
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index d227f6fe86..eecf923046 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -9,7 +9,6 @@ class Comment < ActiveRecord::Base
belongs_to :post, counter_cache: true
belongs_to :author, polymorphic: true
belongs_to :resource, polymorphic: true
- belongs_to :developer
has_many :ratings
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8863736943..dc8bdb98ff 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -814,9 +814,10 @@ ActiveRecord::Schema.define do
t.index :id, unique: true
end
- create_table :subscribers, force: true do |t|
+ create_table :subscribers, id: false, force: true do |t|
t.string :nick, null: false
t.string :name
+ t.integer :id
t.integer :books_count, null: false, default: 0
t.integer :update_count, null: false, default: 0
t.index :nick, unique: true