diff options
Diffstat (limited to 'activerecord/test/cases')
100 files changed, 1366 insertions, 824 deletions
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 99e3d7021d..ecf1368a91 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -193,7 +193,7 @@ module ActiveRecord author = Author.create!(name: 'john') Post.create!(author: author, title: 'foo', body: 'bar') query = author.posts.where(title: 'foo').select(:title) - assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes)) assert_equal({"title" => "foo"}, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) @@ -203,7 +203,7 @@ module ActiveRecord def test_select_methods_passing_a_relation Post.create!(title: 'foo', body: 'bar') query = Post.where(title: 'foo').select(:title) - assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes)) assert_equal({"title" => "foo"}, @connection.select_one(query)) assert @connection.select_all(query).is_a?(ActiveRecord::Result) assert_equal "foo", @connection.select_value(query) diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ce01b16362..4762ef43b5 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -94,7 +94,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -106,10 +106,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_exec_typecasts_bind_vals with_example_table do @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @connection.columns('ex').find { |col| col.name == 'id' } + bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new) result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [bind]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 85db8f4614..1e6511620a 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -129,10 +129,10 @@ module ActiveRecord private def insert(ctx, data, table='ex') - binds = data.map { |name, value| - [ctx.columns(table).find { |x| x.name == name }, value] + binds = data.map { |name, value| + Relation::QueryAttribute.new(name, value, Type::Value.new) } - columns = binds.map(&:first).map(&:name) + columns = binds.map(&:name) sql = "INSERT INTO #{table} (#{columns.join(", ")}) VALUES (#{(['?'] * columns.length).join(', ')})" diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 9c49599d34..06b0cb5515 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -16,7 +16,7 @@ module ActiveRecord def test_initializes_schema_migrations_for_encoding_utf8mb4 smtn = ActiveRecord::Migrator.schema_migrations_table_name - connection.drop_table(smtn) if connection.table_exists?(smtn) + connection.drop_table smtn, if_exists: true config = connection.instance_variable_get(:@config) original_encoding = config[:encoding] diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 042beab23f..b908b9b394 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -25,6 +25,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end end @column = PgArray.columns_hash['tags'] + @type = PgArray.type_for_attribute("tags") end teardown do @@ -35,14 +36,15 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type assert_equal "character varying", @column.sql_type - assert @column.array - assert_not @column.number? - assert_not @column.binary? + assert @column.array? + assert_not @type.number? + assert_not @type.binary? ratings_column = PgArray.columns_hash['ratings'] assert_equal :integer, ratings_column.type - assert ratings_column.array - assert_not ratings_column.number? + type = PgArray.type_for_attribute("ratings") + assert ratings_column.array? + assert_not type.number? end def test_default @@ -74,7 +76,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal :text, column.type assert_equal [], PgArray.column_defaults['snippets'] - assert column.array + assert column.array? end def test_change_column_cant_make_non_array_column_to_array @@ -94,9 +96,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end def test_type_cast_array - assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}')) - assert_equal([], @column.type_cast_from_database('{}')) - assert_equal([nil], @column.type_cast_from_database('{NULL}')) + assert_equal(['1', '2', '3'], @type.type_cast_from_database('{1,2,3}')) + assert_equal([], @type.type_cast_from_database('{}')) + assert_equal([nil], @type.type_cast_from_database('{NULL}')) end def test_type_cast_integers @@ -112,8 +114,8 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema "pg_arrays" - assert_match %r[t.string\s+"tags",\s+array: true], output - assert_match %r[t.integer\s+"ratings",\s+array: true], output + assert_match %r[t\.string\s+"tags",\s+array: true], output + assert_match %r[t\.integer\s+"ratings",\s+array: true], output end def test_select_with_strings @@ -208,7 +210,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase x = PgArray.create!(tags: tags) x.reload - assert_equal x.tags_before_type_cast, PgArray.columns_hash['tags'].type_cast_for_database(tags) + assert_equal x.tags_before_type_cast, PgArray.type_for_attribute('tags').type_cast_for_database(tags) end def test_quoting_non_standard_delimiters diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 72222c01fd..ad433b9d1b 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -26,18 +26,22 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase column = PostgresqlBitString.columns_hash["a_bit"] assert_equal :bit, column.type assert_equal "bit(8)", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlBitString.type_for_attribute("a_bit") + assert_not type.number? + assert_not type.binary? end def test_bit_string_varying_column column = PostgresqlBitString.columns_hash["a_bit_varying"] assert_equal :bit_varying, column.type assert_equal "bit varying(4)", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlBitString.type_for_attribute("a_bit_varying") + assert_not type.number? + assert_not type.binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index aeebec034d..99a7e88178 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -17,6 +17,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase end end @column = ByteaDataType.columns_hash['payload'] + @type = ByteaDataType.type_for_attribute("payload") end teardown do @@ -40,16 +41,16 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = "\u001F\x8B" assert_equal('UTF-8', data.encoding.name) - assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name) + assert_equal('ASCII-8BIT', @type.type_cast_from_database(data).encoding.name) end def test_type_cast_binary_value data = "\u001F\x8B".force_encoding("BINARY") - assert_equal(data, @column.type_cast_from_database(data)) + assert_equal(data, @type.type_cast_from_database(data)) end def test_type_case_nil - assert_equal(nil, @column.type_cast_from_database(nil)) + assert_equal(nil, @type.type_cast_from_database(nil)) end def test_read_value diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index 85bff979c9..d7e611ac46 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -32,9 +32,11 @@ if ActiveRecord::Base.connection.supports_extensions? column = Citext.columns_hash['cival'] assert_equal :citext, column.type assert_equal 'citext', column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Citext.type_for_attribute('cival') + assert_not type.number? + assert_not type.binary? end def test_change_table_supports_json @@ -72,7 +74,7 @@ if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_with_shorthand output = dump_table_schema("citexts") - assert_match %r[t.citext "cival"], output + assert_match %r[t\.citext "cival"], output end end end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index cfab5ca902..be0025218e 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -50,9 +50,11 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase column = PostgresqlComposite.columns_hash["address"] assert_nil column.type assert_equal "full_address", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlComposite.type_for_attribute("address") + assert_not type.number? + assert_not type.binary? end def test_composite_mapping @@ -111,9 +113,11 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase column = PostgresqlComposite.columns_hash["address"] assert_equal :full_address, column.type assert_equal "full_address", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlComposite.type_for_attribute("address") + assert_not type.number? + assert_not type.binary? end def test_composite_mapping diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index ab7fd3c6d5..7bf8d12eae 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -126,11 +126,11 @@ module ActiveRecord end def test_statement_key_is_logged - bindval = 1 - @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]]) + bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new) + @connection.exec_query('SELECT $1::integer', 'SQL', [bind]) name = @subscriber.payloads.last[:statement_name] assert name - res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})") + res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)") plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first assert_operator plan.length, :>, 0 end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 1500adb42d..d0d70122e9 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -29,9 +29,11 @@ class PostgresqlDomainTest < ActiveRecord::TestCase column = PostgresqlDomain.columns_hash["price"] assert_equal :decimal, column.type assert_equal "custom_money", column.sql_type - assert column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlDomain.type_for_attribute("price") + assert type.number? + assert_not type.binary? end def test_domain_acts_like_basetype diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 83cedc5a7b..dab3f92ab2 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -31,9 +31,11 @@ class PostgresqlEnumTest < ActiveRecord::TestCase column = PostgresqlEnum.columns_hash["current_mood"] assert_equal :enum, column.type assert_equal "mood", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlEnum.type_for_attribute("current_mood") + assert_not type.number? + assert_not type.binary? end def test_enum_defaults diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index dca35422b9..a38291cb0a 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -21,9 +21,11 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase column = Tsvector.columns_hash["text_vector"] assert_equal :tsvector, column.type assert_equal "tsvector", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Tsvector.type_for_attribute("text_vector") + assert_not type.number? + assert_not type.binary? end def test_update_tsvector @@ -39,6 +41,6 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("tsvectors") - assert_match %r{t.tsvector "text_vector"}, output + assert_match %r{t\.tsvector "text_vector"}, output end end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 228221e034..39b8daaa83 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -26,9 +26,11 @@ class PostgresqlPointTest < ActiveRecord::TestCase column = PostgresqlPoint.columns_hash["x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlPoint.type_for_attribute("x") + assert_not type.number? + assert_not type.binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 00ff456e16..7ed15b2be0 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -29,6 +29,7 @@ if ActiveRecord::Base.connection.supports_extensions? end end @column = Hstore.columns_hash['tags'] + @type = Hstore.type_for_attribute("tags") end teardown do @@ -54,9 +55,10 @@ if ActiveRecord::Base.connection.supports_extensions? def test_column assert_equal :hstore, @column.type assert_equal "hstore", @column.sql_type - assert_not @column.number? - assert_not @column.binary? - assert_not @column.array + assert_not @column.array? + + assert_not @type.number? + assert_not @type.binary? end def test_default @@ -110,10 +112,10 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_type_cast_hstore - assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\"")) - assert_equal({}, @column.type_cast_from_database("")) - assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) + assert_equal({'1' => '2'}, @type.type_cast_from_database("\"1\"=>\"2\"")) + assert_equal({}, @type.type_cast_from_database("")) + assert_equal({'key'=>nil}, @type.type_cast_from_database('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @type.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) end def test_with_store_accessors @@ -165,47 +167,47 @@ if ActiveRecord::Base.connection.supports_extensions? end def test_gen1 - assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''})) + assert_equal(%q(" "=>""), @type.type_cast_for_database({' '=>''})) end def test_gen2 - assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''})) + assert_equal(%q(","=>""), @type.type_cast_for_database({','=>''})) end def test_gen3 - assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''})) + assert_equal(%q("="=>""), @type.type_cast_for_database({'='=>''})) end def test_gen4 - assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''})) + assert_equal(%q(">"=>""), @type.type_cast_for_database({'>'=>''})) end def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @type.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) end def test_parse2 - assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ ")) + assert_equal({" " => " "}, @type.type_cast_from_database("\\ =>\\ ")) end def test_parse3 - assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>")) + assert_equal({"=" => ">"}, @type.type_cast_from_database("==>>")) end def test_parse4 - assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w')) + assert_equal({"=a"=>"q=w"}, @type.type_cast_from_database('\=a=>q=w')) end def test_parse5 - assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w')) + assert_equal({"=a"=>"q=w"}, @type.type_cast_from_database('"=a"=>q\=w')) end def test_parse6 - assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w')) + assert_equal({"\"a"=>"q>w"}, @type.type_cast_from_database('"\"a"=>q>w')) end def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w')) + assert_equal({"\"a"=>"q\"w"}, @type.type_cast_from_database('\"a=>q"w')) end def test_rewrite @@ -317,7 +319,7 @@ if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_with_shorthand output = dump_table_schema("hstores") - assert_match %r[t.hstore "tags",\s+default: {}], output + assert_match %r[t\.hstore "tags",\s+default: {}], output end private diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 340ca29c0e..ba72a1fdcb 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -34,9 +34,11 @@ module PostgresqlJSONSharedTestCases column = JsonDataType.columns_hash["payload"] assert_equal column_type, column.type assert_equal column_type.to_s, column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = JsonDataType.type_for_attribute("payload") + assert_not type.number? + assert_not type.binary? end def test_default @@ -66,7 +68,7 @@ module PostgresqlJSONSharedTestCases def test_schema_dumping output = dump_table_schema("json_data_type") - assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output) + assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output) end def test_cast_value_on_write @@ -78,16 +80,16 @@ module PostgresqlJSONSharedTestCases end def test_type_cast_json - column = JsonDataType.columns_hash["payload"] + type = JsonDataType.type_for_attribute("payload") data = "{\"a_key\":\"a_value\"}" - hash = column.type_cast_from_database(data) + hash = type.type_cast_from_database(data) assert_equal({'a_key' => 'a_value'}, hash) - assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data)) + assert_equal({'a_key' => 'a_value'}, type.type_cast_from_database(data)) - assert_equal({}, column.type_cast_from_database("{}")) - assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) + assert_equal({}, type.type_cast_from_database("{}")) + assert_equal({'key'=>nil}, type.type_cast_from_database('{"key": null}')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"}))) end def test_rewrite @@ -179,6 +181,14 @@ module PostgresqlJSONSharedTestCases assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload) assert_not json.changed? end + + def test_assigning_invalid_json + json = JsonDataType.new + + json.payload = 'foo' + + assert_nil json.payload + end end class PostgresqlJSONTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 5a0f505072..cdc3269408 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -30,9 +30,11 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase column = Ltree.columns_hash['path'] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = Ltree.type_for_attribute('path') + assert_not type.number? + assert_not type.binary? end def test_write @@ -48,6 +50,6 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("ltrees") - assert_match %r[t.ltree "path"], output + assert_match %r[t\.ltree "path"], output end end diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 54cff192c1..ed1b7d0a1c 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -25,9 +25,11 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase assert_equal :money, column.type assert_equal "money", column.sql_type assert_equal 2, column.scale - assert column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlMoney.type_for_attribute("wealth") + assert type.number? + assert_not type.binary? end def test_default @@ -46,17 +48,17 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase end def test_money_type_cast - column = PostgresqlMoney.columns_hash['wealth'] - assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12")) - assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12")) - assert_equal(-1.15, column.type_cast_from_user("-$1.15")) - assert_equal(-2.25, column.type_cast_from_user("($2.25)")) + type = PostgresqlMoney.type_for_attribute('wealth') + assert_equal(12345678.12, type.type_cast_from_user("$12,345,678.12")) + assert_equal(12345678.12, type.type_cast_from_user("$12.345.678,12")) + assert_equal(-1.15, type.type_cast_from_user("-$1.15")) + assert_equal(-2.25, type.type_cast_from_user("($2.25)")) end def test_schema_dumping output = dump_table_schema("postgresql_moneys") assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output - assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output + assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150\.55$}, output end def test_create_and_update_money diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index 4e49ea1e02..cb75faa1e5 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -23,27 +23,33 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase column = PostgresqlNetworkAddress.columns_hash["cidr_address"] assert_equal :cidr, column.type assert_equal "cidr", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("cidr_address") + assert_not type.number? + assert_not type.binary? end def test_inet_column column = PostgresqlNetworkAddress.columns_hash["inet_address"] assert_equal :inet, column.type assert_equal "inet", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("inet_address") + assert_not type.number? + assert_not type.binary? end def test_macaddr_column column = PostgresqlNetworkAddress.columns_hash["mac_address"] assert_equal :macaddr, column.type assert_equal "macaddr", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = PostgresqlNetworkAddress.type_for_attribute("mac_address") + assert_not type.number? + assert_not type.binary? end def test_network_types @@ -85,8 +91,8 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("postgresql_network_addresses") - assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output - assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output - assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output + assert_match %r{t\.inet\s+"inet_address",\s+default: "192\.168\.1\.1"}, output + assert_match %r{t\.cidr\s+"cidr_address",\s+default: "192\.168\.1\.0/24"}, output + assert_match %r{t\.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 6bb2b26cd5..15c2f48ede 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -284,7 +284,7 @@ module ActiveRecord string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) + 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -298,9 +298,9 @@ module ActiveRecord string = @connection.quote('foo') @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - column = @connection.columns('ex').find { |col| col.name == 'id' } + bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new) result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) + 'SELECT id, data FROM ex WHERE id = $1', nil, [bind]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -437,10 +437,10 @@ module ActiveRecord private def insert(ctx, data) - binds = data.map { |name, value| - [ctx.columns('ex').find { |x| x.name == name }, value] + binds = data.map { |name, value| + bind_param(value, name) } - columns = binds.map(&:first).map(&:name) + columns = binds.map(&:name) bind_subs = columns.length.times.map { |x| "$#{x + 1}" } @@ -457,6 +457,10 @@ module ActiveRecord def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end + + def bind_param(value, name = nil) + ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new) + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 9ac0036d66..894cf1ffa2 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -34,13 +34,14 @@ module ActiveRecord def test_quote_range range = "1,2]'; SELECT * FROM users; --".."a" - c = PostgreSQLColumn.new(nil, nil, OID::Range.new(Type::Integer.new, :int8range)) - assert_equal "'[1,0]'", @conn.quote(range, c) + type = OID::Range.new(Type::Integer.new, :int8range) + assert_equal "'[1,0]'", @conn.quote(type.type_cast_for_database(range)) end def test_quote_bit_string - c = PostgreSQLColumn.new(nil, 1, OID::Bit.new) - assert_equal nil, @conn.quote("'); SELECT * FROM users; /*\n01\n*/--", c) + value = "'); SELECT * FROM users; /*\n01\n*/--" + type = OID::Bit.new + assert_equal nil, @conn.quote(type.type_cast_for_database(value)) end end end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index d812cd01c4..70cf21100a 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -230,36 +230,14 @@ _SQL assert_nil_round_trip(@first_range, :int8_range, 39999...39999) end - def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated - tz = ::ActiveRecord::Base.default_timezone - - silence_warnings { - assert_deprecated { - range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") - assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range - } - assert_deprecated { - range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range - } - assert_deprecated { - range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") - assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range - } - assert_deprecated { - range = PostgresqlRange.create!(int4_range: "(1, 10]") - assert_equal 2..10, range.int4_range - } - assert_deprecated { - range = PostgresqlRange.create!(int8_range: "(10, 100]") - assert_equal 11..100, range.int8_range - } - } - end - def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") } assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") } + assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") } + assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") } + assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") } end def test_update_all_with_ranges diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 99c26c4bf7..6937145439 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase set_session_auth USERS.each do |u| set_session_auth u - assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] set_session_auth end end @@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase USERS.each do |u| @connection.clear_cache! set_session_auth u - assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name'] set_session_auth end end @@ -111,4 +111,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase @connection.session_auth = auth || 'default' end + def bind_param(value) + ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new) + end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index e99f1e2867..83e35ad1a1 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -151,10 +151,10 @@ class SchemaTest < ActiveRecord::TestCase def test_schema_change_with_prepared_stmt altered = false - @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] + @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)] @connection.exec_query "alter table developers add column zomg int", 'sql', [] altered = true - @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] + @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)] ensure # We are not using DROP COLUMN IF EXISTS because that syntax is only # supported by pg 9.X @@ -435,6 +435,10 @@ class SchemaTest < ActiveRecord::TestCase assert_equal this_index_column, this_index.columns[0] assert_equal this_index_name, this_index.name end + + def bind_param(value) + ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new) + end end class SchemaForeignKeyTest < ActiveRecord::TestCase @@ -454,7 +458,7 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase end @connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id" output = dump_table_schema "wagons" - assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output + assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output ensure @connection.execute "DROP TABLE IF EXISTS wagons" @connection.execute "DROP TABLE IF EXISTS my_schema.trains" diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index c88259d274..4506e874bc 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -18,8 +18,8 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]") big_array = [123456789123456789] - assert_raises(RangeError) { int_array.type_cast_from_user(big_array) } - assert_equal big_array, bigint_array.type_cast_from_user(big_array) + assert_raises(RangeError) { int_array.type_cast_for_database(big_array) } + assert_equal "{123456789123456789}", bigint_array.type_cast_for_database(big_array) end test "range types correctly respect registration of subtypes" do diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index d5d2dd16e2..ad86d617b8 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -49,9 +49,11 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type assert_equal "uuid", column.sql_type - assert_not column.number? - assert_not column.binary? - assert_not column.array + assert_not column.array? + + type = UUIDType.type_for_attribute("guid") + assert_not type.number? + assert_not type.binary? end def test_treat_blank_uuid_as_nil @@ -114,7 +116,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema "uuid_data_type" - assert_match %r{t.uuid "guid"}, output + assert_match %r{t\.uuid "guid"}, output end def test_uniqueness_validation_ignores_uuid diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index 5aba118518..ac8464a65d 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -50,6 +50,6 @@ class PostgresqlXMLTest < ActiveRecord::TestCase def test_schema_dump_with_shorthand output = dump_table_schema("xml_data_type") - assert_match %r{t.xml "payload"}, output + assert_match %r{t\.xml "payload"}, output end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index df497e761c..c1d9b7c273 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -85,9 +85,9 @@ module ActiveRecord def test_quoting_binary_strings value = "hello".encode('ascii-8bit') - column = Column.new(nil, 1, SQLite3String.new) + type = Type::String.new - assert_equal "'hello'", @conn.quote(value, column) + assert_equal "'hello'", @conn.quote(type.type_cast_for_database(value)) end end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 029663e7f4..0527bf8b15 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -83,8 +83,7 @@ module ActiveRecord def test_exec_insert with_example_table do - column = @conn.columns('ex').find { |col| col.name == 'number' } - vals = [[column, 10]] + vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)] @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals) result = @conn.exec_query( @@ -157,7 +156,7 @@ module ActiveRecord with_example_table 'id int, data string' do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -169,10 +168,9 @@ module ActiveRecord def test_exec_query_typecasts_bind_vals with_example_table 'id int, data string' do @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @conn.columns('ex').find { |col| col.name == 'id' } result = @conn.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)]) assert_equal 1, result.rows.length assert_equal 2, result.columns.length diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 025c9fe6f9..f4e8003bc3 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -93,69 +93,38 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") end - def test_timestamps_without_null_is_deprecated_on_create_table - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps - end + def test_timestamps_without_null_set_null_to_false_on_create_table + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps end end + + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null end - def test_timestamps_without_null_is_deprecated_on_change_table - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps + def test_timestamps_without_null_set_null_to_false_on_change_table + ActiveRecord::Schema.define do + create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps - end + change_table :has_timestamps do |t| + t.timestamps default: Time.now end end - end - def test_timestamps_without_null_is_deprecated_on_add_timestamps - assert_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps - add_timestamps :has_timestamps - end - end + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null end - def test_no_deprecation_warning_from_timestamps_on_create_table - assert_not_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps null: true - end - - drop_table :has_timestamps - - create_table :has_timestamps do |t| - t.timestamps null: false - end - end + def test_timestamps_without_null_set_null_to_false_on_add_timestamps + ActiveRecord::Schema.define do + create_table :has_timestamps + add_timestamps :has_timestamps, default: Time.now end - end - - def test_no_deprecation_warning_from_timestamps_on_change_table - assert_not_deprecated do - ActiveRecord::Schema.define do - create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps null: true - end - drop_table :has_timestamps - - create_table :has_timestamps - change_table :has_timestamps do |t| - t.timestamps null: false, default: Time.now - end - end - end + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null end end end diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index 3e0032ec73..472e270f8c 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -8,12 +8,7 @@ module ActiveRecord test 'does not duplicate conditions' do scope = AssociationScope.scope(Author.new.association(:welcome_posts), Author.connection) - wheres = scope.where_values.map(&:right) - binds = scope.bind_values.map(&:last) - wheres = scope.where_values.map(&:right).reject { |node| - Arel::Nodes::BindParam === node - } - assert_equal wheres.uniq, wheres + binds = scope.where_clause.binds.map(&:value) assert_equal binds.uniq, binds end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 17394cb6f7..a425b3ed88 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -122,14 +122,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first - assert citibank_result.association_cache.key?(:firm_with_primary_key) + assert citibank_result.association(:firm_with_primary_key).loaded? end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first - assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols) + assert citibank_result.association(:firm_with_primary_key_symbols).loaded? end def test_creating_the_belonging_object diff --git a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb b/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb deleted file mode 100644 index 48f7ddbe83..0000000000 --- a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require "cases/helper" - -class DeprecatedCounterCacheOnHasManyThroughTest < ActiveRecord::TestCase - class Post < ActiveRecord::Base - has_many :taggings, as: :taggable - has_many :tags, through: :taggings - end - - class Tagging < ActiveRecord::Base - belongs_to :taggable, polymorphic: true - belongs_to :tag - end - - class Tag < ActiveRecord::Base - end - - test "counter caches are updated in the database if the belongs_to association doesn't specify a counter cache" do - post = Post.create!(title: 'Hello', body: 'World!') - assert_deprecated { post.tags << Tag.create!(name: 'whatever') } - - assert_equal 1, post.tags.size - assert_equal 1, post.tags_count - assert_equal 1, post.reload.tags.size - assert_equal 1, post.reload.tags_count - end -end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 1d00177405..7d8b933992 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -834,18 +834,6 @@ class EagerAssociationTest < ActiveRecord::TestCase ) end - def test_preload_with_interpolation - assert_deprecated do - post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id) - assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions - end - - assert_deprecated do - post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id) - assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions - end - end - def test_polymorphic_type_condition post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) @@ -1302,23 +1290,22 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet end - test "include instance dependent associations is deprecated" do + test "preloading and eager loading of instance dependent associations is not supported" do message = "association scope 'posts_with_signature' is" - assert_deprecated message do - begin - Author.includes(:posts_with_signature).to_a - rescue NoMethodError - # it's expected that preloading of this association fails - end + error = assert_raises(ArgumentError) do + Author.includes(:posts_with_signature).to_a end + assert_match message, error.message - assert_deprecated message do - Author.preload(:posts_with_signature).to_a rescue NoMethodError + error = assert_raises(ArgumentError) do + Author.preload(:posts_with_signature).to_a end + assert_match message, error.message - assert_deprecated message do + error = assert_raises(ArgumentError) do Author.eager_load(:posts_with_signature).to_a end + assert_match message, error.message end test "preloading readonly association" do diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 21a45042fa..19f4384e83 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -316,16 +316,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope car = cars(:honda) - scoped_count = car.foo_bulbs.where_values.count + scope = car.foo_bulbs.where_values_hash bulb = car.foo_bulbs.build - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = car.foo_bulbs.create - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = car.foo_bulbs.create! - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash end def test_no_sql_should_be_fired_if_association_already_loaded @@ -1307,7 +1307,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { topic.destroy } end - uses_transaction :test_dependence_with_transaction_support_on_failure def test_dependence_with_transaction_support_on_failure firm = companies(:first_firm) clients = firm.clients diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 9b6757e256..3b6a4038cd 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -237,16 +237,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) - scoped_count = pirate.association(:foo_bulb).scope.where_values.count + scope = pirate.association(:foo_bulb).scope.where_values_hash bulb = pirate.build_foo_bulb - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = pirate.create_foo_bulb - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash bulb = pirate.create_foo_bulb! - assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count + assert_not_equal scope, bulb.scope_after_initialize.where_values_hash end def test_create_association @@ -276,7 +276,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_create_with_inexistent_foreign_key_failing firm = Firm.create(name: 'GlobalMegaCorp') - assert_raises(ActiveRecord::UnknownAttributeError) do + assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) do firm.create_account_with_inexistent_foreign_key end end diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index 321fb6c8dd..8b765a2e0c 100644 --- a/activerecord/test/cases/associations/required_test.rb +++ b/activerecord/test/cases/associations/required_test.rb @@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase end teardown do - @connection.drop_table 'parents' if @connection.table_exists? 'parents' - @connection.drop_table 'children' if @connection.table_exists? 'children' + @connection.drop_table 'parents', if_exists: true + @connection.drop_table 'children', if_exists: true end test "belongs_to associations are not required by default" do @@ -40,7 +40,7 @@ class RequiredAssociationsTest < ActiveRecord::TestCase record = model.new assert_not record.save - assert_equal ["Parent can't be blank"], record.errors.full_messages + assert_equal ["Parent must exist"], record.errors.full_messages record.parent = Parent.new assert record.save @@ -64,12 +64,32 @@ class RequiredAssociationsTest < ActiveRecord::TestCase record = model.new assert_not record.save - assert_equal ["Child can't be blank"], record.errors.full_messages + assert_equal ["Child must exist"], record.errors.full_messages record.child = Child.new assert record.save end + test "required has_one associations have a correct error message" do + model = subclass_of(Parent) do + has_one :child, required: true, inverse_of: false, + class_name: "RequiredAssociationsTest::Child" + end + + record = model.create + assert_equal ["Child must exist"], record.errors.full_messages + end + + test "required belongs_to associations have a correct error message" do + model = subclass_of(Child) do + belongs_to :parent, required: true, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end + + record = model.create + assert_equal ["Parent must exist"], record.errors.full_messages + end + private def subclass_of(klass, &block) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 72963fd56c..de358114ab 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -141,7 +141,7 @@ class AssociationsTest < ActiveRecord::TestCase def test_association_with_references firm = companies(:first_firm) - assert_equal ['foo'], firm.association_with_references.references_values + assert_includes firm.association_with_references.references_values, 'foo' end end @@ -238,7 +238,7 @@ class AssociationProxyTest < ActiveRecord::TestCase end def test_scoped_allows_conditions - assert developers(:david).projects.merge!(where: 'foo').where_values.include?('foo') + assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo') end test "getting a scope from an association" do diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb index 53bd58e22e..9ad02ffae8 100644 --- a/activerecord/test/cases/attribute_decorators_test.rb +++ b/activerecord/test/cases/attribute_decorators_test.rb @@ -28,7 +28,7 @@ module ActiveRecord teardown do return unless @connection - @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model' + @connection.drop_table 'attribute_decorators_model', if_exists: true Model.attribute_type_decorations.clear Model.reset_column_information end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index e38b32d7fc..74e556211b 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -17,7 +17,7 @@ module ActiveRecord include ActiveRecord::AttributeMethods - def self.column_names + def self.attribute_names %w{ one two three } end @@ -25,11 +25,11 @@ module ActiveRecord end def self.columns - column_names.map { FakeColumn.new(name) } + attribute_names.map { FakeColumn.new(name) } end def self.columns_hash - Hash[column_names.map { |name| + Hash[attribute_names.map { |name| [name, FakeColumn.new(name)] }] end @@ -39,13 +39,13 @@ module ActiveRecord def test_define_attribute_methods instance = @klass.new - @klass.column_names.each do |name| + @klass.attribute_names.each do |name| assert !instance.methods.map(&:to_s).include?(name) end @klass.define_attribute_methods - @klass.column_names.each do |name| + @klass.attribute_names.each do |name| assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined" end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 731d433706..243c90e945 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -502,7 +502,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_false Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT - if current_adapter?(:OracleAdapter) + if current_adapter?(:OracleAdapter, :FbAdapter) topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first @@ -513,7 +513,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_true Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT - if current_adapter?(:OracleAdapter) + if current_adapter?(:OracleAdapter, :FbAdapter) topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first else topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first @@ -531,20 +531,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_deprecated_cache_attributes - assert_deprecated do - Topic.cache_attributes :replies_count - end - - assert_deprecated do - Topic.cached_attributes - end - - assert_deprecated do - Topic.cache_attribute? :replies_count - end - end - def test_converted_values_are_returned_after_assignment developer = Developer.new(name: 1337, salary: "50000") @@ -671,7 +657,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_in_current_time_zone + def test_setting_time_zone_aware_datetime_in_current_time_zone utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -690,6 +676,47 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_aware_time_in_current_time_zone + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + time_string = "10:00:00" + expected_time = Time.zone.parse("2000-01-01 #{time_string}") + + record.bonus_time = time_string + assert_equal expected_time, record.bonus_time + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone + + record.bonus_time = '' + assert_nil record.bonus_time + end + end + + def test_setting_time_zone_aware_time_with_dst + in_time_zone "Pacific Time (US & Canada)" do + current_time = Time.zone.local(2014, 06, 15, 10) + record = @target.new(bonus_time: current_time) + time_before_save = record.bonus_time + + record.save + record.reload + + assert_equal time_before_save, record.bonus_time + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone + end + end + + def test_removing_time_zone_aware_types + with_time_zone_aware_types(:datetime) do + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new(bonus_time: "10:00:00") + expected_time = Time.utc(2000, 01, 01, 10) + + assert_equal expected_time, record.bonus_time + assert record.bonus_time.utc? + end + end + end + def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] @@ -731,12 +758,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_bulk_update_respects_access_control privatize("title=(value)") - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") } - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } + assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new(:title => "Rants about pants") } + assert_raise(ActiveModel::AttributeAssignment::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end def test_bulk_update_raise_unknown_attribute_error - error = assert_raises(ActiveRecord::UnknownAttributeError) { + error = assert_raises(ActiveModel::AttributeAssignment::UnknownAttributeError) { Topic.new(hello: "world") } assert_instance_of Topic, error.record @@ -902,6 +929,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_not_equal ['id'], @target.column_names end + def test_came_from_user + model = @target.first + + assert_not model.id_came_from_user? + model.id = "omg" + assert model.id_came_from_user? + end + + def test_accessed_fields + model = @target.first + + assert_equal [], model.accessed_fields + + model.title + + assert_equal ["title"], model.accessed_fields + end + private def new_topic_like_ar_class(&block) @@ -914,6 +959,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase klass end + def with_time_zone_aware_types(*types) + old_types = ActiveRecord::Base.time_zone_aware_types + ActiveRecord::Base.time_zone_aware_types = types + yield + ensure + ActiveRecord::Base.time_zone_aware_types = old_types + end + def cached_columns Topic.columns.map(&:name) end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index ba53612d30..8025c7c4d2 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -186,5 +186,16 @@ module ActiveRecord attributes.freeze assert_equal({ foo: "1" }, attributes.to_hash) end + + test "#accessed_attributes returns only attributes which have been read" do + builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new) + attributes = builder.build_from_database(foo: "1", bar: "2") + + assert_equal [], attributes.accessed + + attributes.fetch_value(:foo) + + assert_equal [:foo], attributes.accessed + end end end diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index 7b325abf1d..eac73e11e8 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -5,6 +5,7 @@ module ActiveRecord class AttributeTest < ActiveRecord::TestCase setup do @type = Minitest::Mock.new + @type.expect(:==, false, [false]) end teardown do @@ -168,5 +169,24 @@ module ActiveRecord second = Attribute.from_user(:foo, 1, Type::Integer.new) assert_not_equal first, second end + + test "an attribute has not been read by default" do + attribute = Attribute.from_database(:foo, 1, Type::Value.new) + assert_not attribute.has_been_read? + end + + test "an attribute has been read when its value is calculated" do + attribute = Attribute.from_database(:foo, 1, Type::Value.new) + attribute.value + assert attribute.has_been_read? + end + + test "an attribute can not be mutated if it has not been read, + and skips expensive calculations" do + type_which_raises_from_all_methods = Object.new + attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods) + + assert_not attribute.changed_in_place_from?("bar") + end end end diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index dbe1eb48db..6f331c5985 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -50,15 +50,15 @@ module ActiveRecord end test "overloaded properties with limit" do - assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit - assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit + assert_equal 50, OverloadedType.type_for_attribute('overloaded_string_with_limit').limit + assert_equal 255, UnoverloadedType.type_for_attribute('overloaded_string_with_limit').limit end test "nonexistent attribute" do data = OverloadedType.new(non_existent_decimal: 1) assert_equal BigDecimal.new(1), data.non_existent_decimal - assert_raise ActiveRecord::UnknownAttributeError do + assert_raise ActiveModel::AttributeAssignment::UnknownAttributeError do UnoverloadedType.new(non_existent_decimal: 1) end end @@ -87,29 +87,42 @@ module ActiveRecord assert_equal 4.4, data.overloaded_float end - test "overloading properties does not change column order" do - column_names = OverloadedType.column_names - assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names + test "overloading properties does not attribute method order" do + attribute_names = OverloadedType.attribute_names + assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), attribute_names end test "caches are cleared" do klass = Class.new(OverloadedType) - assert_equal 6, klass.columns.length - assert_not klass.columns_hash.key?('wibble') - assert_equal 6, klass.column_types.length + assert_equal 6, klass.attribute_types.length assert_equal 6, klass.column_defaults.length - assert_not klass.column_names.include?('wibble') - assert_equal 5, klass.content_columns.length + assert_not klass.attribute_types.include?('wibble') klass.attribute :wibble, Type::Value.new - assert_equal 7, klass.columns.length - assert klass.columns_hash.key?('wibble') - assert_equal 7, klass.column_types.length + assert_equal 7, klass.attribute_types.length assert_equal 7, klass.column_defaults.length - assert klass.column_names.include?('wibble') - assert_equal 6, klass.content_columns.length + assert klass.attribute_types.include?('wibble') + end + + test "the given default value is cast from user" do + custom_type = Class.new(Type::Value) do + def type_cast_from_user(*) + "from user" + end + + def type_cast_from_database(*) + "from database" + end + end + + klass = Class.new(OverloadedType) do + attribute :wibble, custom_type.new, default: "default" + end + model = klass.new + + assert_equal "from user", model.wibble end end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 04d5a2869c..859afc4553 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1030,6 +1030,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase assert_equal 'The Vile Insanity', @pirate.reload.ship.name end + def test_changed_for_autosave_should_handle_cycles + @ship.pirate = @pirate + assert_queries(0) { @ship.save! } + + @parrot = @pirate.parrots.create(name: "some_name") + @parrot.name="changed_name" + assert_queries(1) { @ship.save! } + assert_queries(0) { @ship.save! } + end + def test_should_automatically_save_bang_the_associated_model @pirate.ship.name = 'The Vile Insanity' @pirate.save! @@ -1051,11 +1061,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_ignore_different_error_messages_on_the_same_attribute + old_validators = Ship._validators.deep_dup + old_callbacks = Ship._validate_callbacks.deep_dup Ship.validates_format_of :name, :with => /\w/ @pirate.ship.name = "" @pirate.catchphrase = nil assert @pirate.invalid? assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"] + ensure + Ship._validators = old_validators if old_validators + Ship._validate_callbacks = old_callbacks if old_callbacks end def test_should_still_allow_to_bypass_validations_on_the_associated_model diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 6acd9aa39f..0debd30e5c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -88,6 +88,7 @@ class BasicsTest < ActiveRecord::TestCase 'Mysql2Adapter' => '`', 'PostgreSQLAdapter' => '"', 'OracleAdapter' => '"', + 'FbAdapter' => '"' }.fetch(classname) { raise "need a bad char for #{classname}" } @@ -111,7 +112,7 @@ class BasicsTest < ActiveRecord::TestCase assert_nil Edge.primary_key end - unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) + unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter) def test_limit_with_comma assert Topic.limit("1,2").to_a end diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index c4634d11e2..68d5a232ba 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -1,9 +1,11 @@ require 'cases/helper' require 'models/topic' +require 'models/author' +require 'models/post' module ActiveRecord class BindParameterTest < ActiveRecord::TestCase - fixtures :topics + fixtures :topics, :authors, :posts class LogListener attr_accessor :calls @@ -20,8 +22,8 @@ module ActiveRecord def setup super @connection = ActiveRecord::Base.connection - @subscriber = LogListener.new - @pk = Topic.columns_hash[Topic.primary_key] + @subscriber = LogListener.new + @pk = ConnectionAdapters::Column.new(Topic.primary_key, nil, Topic.type_for_attribute(Topic.primary_key)) @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end @@ -30,40 +32,34 @@ module ActiveRecord end if ActiveRecord::Base.connection.supports_statement_cache? - def test_binds_are_logged - sub = @connection.substitute_at(@pk) - binds = [[@pk, 1]] - sql = "select * from topics where id = #{sub.to_sql}" - - @connection.exec_query(sql, 'SQL', binds) - - message = @subscriber.calls.find { |args| args[4][:sql] == sql } - assert_equal binds, message[4][:binds] + def test_bind_from_join_in_subquery + subquery = Author.joins(:thinking_posts).where(name: 'David') + scope = Author.from(subquery, 'authors').where(id: 1) + assert_equal 1, scope.count end - def test_binds_are_logged_after_type_cast + def test_binds_are_logged sub = @connection.substitute_at(@pk) - binds = [[@pk, "3"]] + binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)] sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) message = @subscriber.calls.find { |args| args[4][:sql] == sql } - assert_equal [[@pk, 3]], message[4][:binds] + assert_equal binds, message[4][:binds] end def test_find_one_uses_binds Topic.find(1) - binds = [[@pk, 1]] - message = @subscriber.calls.find { |args| args[4][:binds] == binds } + message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } } assert message, 'expected a message with binds' end - def test_logs_bind_vars + def test_logs_bind_vars_after_type_cast payload = { :name => 'SQL', :sql => 'select * from topics where id = ?', - :binds => [[@pk, 10]] + :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)] } event = ActiveSupport::Notifications::Event.new( 'foo', diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 299217214e..f47568f2f5 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -631,4 +631,9 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id)) end + + def test_calculation_grouped_by_association_doesnt_error_when_no_records_have_association + Client.update_all(client_of: nil) + assert_equal({ nil => Client.count }, Client.group(:firm).count) + end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index d4cc081f32..3ae4a6eade 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -49,6 +49,11 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } end +class CallbackDeveloperWithHaltedValidation < CallbackDeveloper + before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) } + before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } +end + class ParentDeveloper < ActiveRecord::Base self.table_name = 'developers' attr_accessor :after_save_called @@ -73,6 +78,20 @@ class ImmutableDeveloper < ActiveRecord::Base end end +class DeveloperWithCanceledCallbacks < ActiveRecord::Base + self.table_name = 'developers' + + validates_inclusion_of :salary, in: 50000..200000 + + before_save :cancel + before_destroy :cancel + + private + def cancel + throw(:abort) + end +end + class OnCallbacksDeveloper < ActiveRecord::Base self.table_name = 'developers' @@ -136,6 +155,23 @@ class CallbackCancellationDeveloper < ActiveRecord::Base after_destroy { @after_destroy_called = true } end +class CallbackHaltedDeveloper < ActiveRecord::Base + self.table_name = 'developers' + + attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called + attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy + + before_save { throw(:abort) if defined?(@cancel_before_save) } + before_create { throw(:abort) if @cancel_before_create } + before_update { throw(:abort) if @cancel_before_update } + before_destroy { throw(:abort) if @cancel_before_destroy } + + after_save { @after_save_called = true } + after_update { @after_update_called = true } + after_create { @after_create_called = true } + after_destroy { @after_destroy_called = true } +end + class CallbacksTest < ActiveRecord::TestCase fixtures :developers @@ -252,7 +288,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], - [ :after_save, :block ] + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -321,7 +362,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], - [ :after_save, :block ] + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -372,7 +418,12 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_destroy, :string ], [ :after_destroy, :proc ], [ :after_destroy, :object ], - [ :after_destroy, :block ] + [ :after_destroy, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :string ], + [ :after_commit, :method ] ], david.history end @@ -393,12 +444,14 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end - def test_before_save_returning_false + def test_deprecated_before_save_returning_false david = ImmutableDeveloper.find(1) - assert david.valid? - assert !david.save - exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } - assert_equal exc.record, david + assert_deprecated do + assert david.valid? + assert !david.save + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal exc.record, david + end david = ImmutableDeveloper.find(1) david.salary = 10_000_000 @@ -408,38 +461,48 @@ class CallbacksTest < ActiveRecord::TestCase someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_save = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_create_returning_false + def test_deprecated_before_create_returning_false someone = CallbackCancellationDeveloper.new someone.cancel_before_create = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_update_returning_false + def test_deprecated_before_update_returning_false someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_update = true - assert someone.valid? - assert !someone.save + assert_deprecated do + assert someone.valid? + assert !someone.save + end assert_save_callbacks_not_called(someone) end - def test_before_destroy_returning_false + def test_deprecated_before_destroy_returning_false david = ImmutableDeveloper.find(1) - assert !david.destroy - exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } - assert_equal exc.record, david + assert_deprecated do + assert !david.destroy + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal exc.record, david + end assert_not_nil ImmutableDeveloper.find_by_id(1) someone = CallbackCancellationDeveloper.find(1) someone.cancel_before_destroy = true - assert !someone.destroy - assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + assert_deprecated do + assert !someone.destroy + assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + end assert !someone.after_destroy_called end @@ -450,9 +513,59 @@ class CallbacksTest < ActiveRecord::TestCase end private :assert_save_callbacks_not_called + def test_before_create_throwing_abort + someone = CallbackHaltedDeveloper.new + someone.cancel_before_create = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_save_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert david.valid? + assert !david.save + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal exc.record, david + + david = DeveloperWithCanceledCallbacks.find(1) + david.salary = 10_000_000 + assert !david.valid? + assert !david.save + assert_raise(ActiveRecord::RecordInvalid) { david.save! } + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_save = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_update_throwing_abort + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_update = true + assert someone.valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_destroy_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert !david.destroy + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal exc.record, david + assert_not_nil ImmutableDeveloper.find_by_id(1) + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_destroy = true + assert !someone.destroy + assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + assert !someone.after_destroy_called + end + def test_callback_returning_false david = CallbackDeveloperWithFalseValidation.find(1) - david.save + assert_deprecated { david.save } assert_equal [ [ :after_find, :method ], [ :after_find, :string ], @@ -478,6 +591,34 @@ class CallbacksTest < ActiveRecord::TestCase ], david.history end + def test_callback_throwing_abort + david = CallbackDeveloperWithHaltedValidation.find(1) + david.save + assert_equal [ + [ :after_find, :method ], + [ :after_find, :string ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :string ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :string ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation, :throwing_abort ], + [ :after_rollback, :block ], + [ :after_rollback, :object ], + [ :after_rollback, :proc ], + [ :after_rollback, :string ], + [ :after_rollback, :method ], + ], david.history + end + def test_inheritance_of_callbacks parent = ParentDeveloper.new assert !parent.after_save_called diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 3e33b30144..b72f8ca88c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -44,9 +44,7 @@ module ActiveRecord end def test_connection_pools - assert_deprecated do - assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools) - end + assert_equal([@pool], @handler.connection_pools) end end end diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb index 37ad469476..9ee92a3cd2 100644 --- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb @@ -51,34 +51,6 @@ module ActiveRecord assert_equal expected, actual end - def test_resolver_with_database_uri_and_and_current_env_string_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("default_env", config) } - expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } - assert_equal expected, actual - end - - def test_resolver_with_database_uri_and_and_current_env_string_key_and_rails_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RAILS_ENV'] = "foo" - - config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("foo", config) } - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } - assert_equal expected, actual - end - - def test_resolver_with_database_uri_and_and_current_env_string_key_and_rack_env - ENV['DATABASE_URL'] = "postgres://localhost/foo" - ENV['RACK_ENV'] = "foo" - - config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } } - actual = assert_deprecated { resolve_spec("foo", config) } - expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" } - assert_equal expected, actual - end - def test_resolver_with_database_uri_and_known_key ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } @@ -95,16 +67,6 @@ module ActiveRecord end end - def test_resolver_with_database_uri_and_unknown_string_key - ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } - assert_deprecated do - assert_raises AdapterNotSpecified do - resolve_spec("production", config) - end - end - end - def test_resolver_with_database_uri_and_supplied_url ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb index 715d92af99..3cb98832c5 100644 --- a/activerecord/test/cases/core_test.rb +++ b/activerecord/test/cases/core_test.rb @@ -98,4 +98,15 @@ class CoreTest < ActiveRecord::TestCase assert actual.start_with?(expected.split('XXXXXX').first) assert actual.end_with?(expected.split('XXXXXX').last) end + + def test_pretty_print_overridden_by_inspect + subtopic = Class.new(Topic) do + def inspect + "inspecting topic" + end + end + actual = '' + PP.pp(subtopic.new, StringIO.new(actual)) + assert_equal "inspecting topic\n", actual + end end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 07a182070b..1f5055b2a2 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -180,4 +180,22 @@ class CounterCacheTest < ActiveRecord::TestCase SpecialTopic.reset_counters(special.id, :lightweight_special_replies) end end + + test "counters are updated both in memory and in the database on create" do + car = Car.new(engines_count: 0) + car.engines = [Engine.new, Engine.new] + car.save! + + assert_equal 2, car.engines_count + assert_equal 2, car.reload.engines_count + end + + test "counter caches are updated in memory when the default value is nil" do + car = Car.new(engines_count: nil) + car.engines = [Engine.new, Engine.new] + car.save! + + assert_equal 2, car.engines_count + assert_equal 2, car.reload.engines_count + end end diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 330232cee2..4cbff564aa 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -55,7 +55,7 @@ class DateTimeTest < ActiveRecord::TestCase now = DateTime.now with_timezone_config default: :local do task = Task.new starting: now - assert now, task.starting + assert_equal now, task.starting end end end diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index e9bc583bf4..b9db0d0123 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -40,7 +40,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase end teardown do - @connection.drop_table "default_numbers" if @connection.table_exists? 'default_numbers' + @connection.drop_table :default_numbers, if_exists: true end def test_default_positive_integer diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 98cf60a8c4..192ba6f7cd 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -165,18 +165,6 @@ class DirtyTest < ActiveRecord::TestCase assert_equal parrot.name_change, parrot.title_change end - def test_reset_attribute! - pirate = Pirate.create!(:catchphrase => 'Yar!') - pirate.catchphrase = 'Ahoy!' - - assert_deprecated do - pirate.reset_catchphrase! - end - assert_equal "Yar!", pirate.catchphrase - assert_equal Hash.new, pirate.changes - assert !pirate.catchphrase_changed? - end - def test_restore_attribute! pirate = Pirate.create!(:catchphrase => 'Yar!') pirate.catchphrase = 'Ahoy!' @@ -720,6 +708,27 @@ class DirtyTest < ActiveRecord::TestCase assert model.first_name_changed? end + test "attribute_will_change! doesn't try to save non-persistable attributes" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'people' + attribute :non_persisted_attribute, ActiveRecord::Type::String.new + end + + record = klass.new(first_name: "Sean") + record.non_persisted_attribute_will_change! + + assert record.non_persisted_attribute_changed? + assert record.save + end + + test "mutating and then assigning doesn't remove the change" do + pirate = Pirate.create!(catchphrase: "arrrr") + pirate.catchphrase << " matey!" + pirate.catchphrase = "arrrr matey!" + + assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!") + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 9d25bdd82a..f1d5511bb8 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_explain? assert_match "SELECT", sql if binds.any? assert_equal 1, binds.length - assert_equal "honda", binds.flatten.last + assert_equal "honda", binds.last.value else assert_match 'honda', sql end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 02dc5d3ad3..39308866ee 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -55,7 +55,7 @@ class FinderTest < ActiveRecord::TestCase end def test_symbols_table_ref - gc_disabled = GC.disable if RUBY_VERSION >= '2.2.0' + gc_disabled = GC.disable Post.where("author_id" => nil) # warm up x = Symbol.all_symbols.count Post.where("title" => {"xxxqqqq" => "bar"}) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 9edeb8b47f..f41245dfd2 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -672,7 +672,8 @@ class FasterFixturesTest < ActiveRecord::TestCase end class FoxyFixturesTest < ActiveRecord::TestCase - fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" + fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, + :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' require 'models/uuid_parent' @@ -792,6 +793,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal("X marks the spot!", pirates(:mark).catchphrase) end + def test_supports_label_interpolation_for_fixnum_label + assert_equal("#1 pirate!", pirates(1).catchphrase) + end + def test_supports_polymorphic_belongs_to assert_equal(pirates(:redbeard), treasures(:sapphire).looter) assert_equal(parrots(:louis), treasures(:ruby).looter) @@ -808,6 +813,12 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal pirates(:blackbeard), parrots(:polly).killer end + def test_supports_sti_with_respective_files + assert_kind_of LiveParrot, live_parrots(:dusty) + assert_kind_of DeadParrot, dead_parrots(:deadbird) + assert_equal pirates(:blackbeard), dead_parrots(:deadbird).killer + end + def test_namespaced_models assert admin_accounts(:signals37).users.include?(admin_users(:david)) assert_equal 2, admin_accounts(:signals37).users.size @@ -830,9 +841,9 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase set_fixture_class :randomly_named_a9 => ClassNameThatDoesNotFollowCONVENTIONS, :'admin/randomly_named_a9' => - Admin::ClassNameThatDoesNotFollowCONVENTIONS, + Admin::ClassNameThatDoesNotFollowCONVENTIONS1, 'admin/randomly_named_b0' => - Admin::ClassNameThatDoesNotFollowCONVENTIONS + Admin::ClassNameThatDoesNotFollowCONVENTIONS2 fixtures :randomly_named_a9, 'admin/randomly_named_a9', :'admin/randomly_named_b0' @@ -843,15 +854,15 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end def test_named_accessor_for_randomly_named_namespaced_fixture_and_class - assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS1, admin_randomly_named_a9(:first_instance) - assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS2, admin_randomly_named_b0(:second_instance) end def test_table_name_is_defined_in_the_model - assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name - assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name + assert_equal 'randomly_named_table2', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal 'randomly_named_table2', Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 80ac57ec7c..0a577fa2f5 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -24,15 +24,15 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -# Enable raise errors in after_commit and after_rollback. -ActiveRecord::Base.raise_in_transactional_callbacks = true - # Connect to the database ARTest.connect # Quote "type" if it's a reserved word for the current connection. QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') +# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends. +ActiveRecord::Base.time_zone_aware_types << :time + def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index fe6323ab02..6b18bfe84f 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -294,12 +294,12 @@ class InheritanceTest < ActiveRecord::TestCase def test_eager_load_belongs_to_something_inherited account = Account.all.merge!(:includes => :firm).find(1) - assert account.association_cache.key?(:firm), "nil proves eager load failed" + assert account.association(:firm).loaded?, "association was not eager loaded" end def test_alt_eager_loading cabbage = RedCabbage.all.merge!(:includes => :seller).find(4) - assert cabbage.association_cache.key?(:seller), "nil proves eager load failed" + assert cabbage.association(:seller).loaded?, "association was not eager loaded" end def test_eager_load_belongs_to_primary_key_quoting diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index ee43f07dd7..848174df06 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -215,10 +215,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase def test_lock_with_custom_column_without_default_sets_version_to_zero t1 = LockWithCustomColumnWithoutDefault.new assert_equal 0, t1.custom_lock_version + assert_nil t1.custom_lock_version_before_type_cast - t1.save - t1 = LockWithCustomColumnWithoutDefault.find(t1.id) + t1.save! + t1.reload assert_equal 0, t1.custom_lock_version + assert [0, "0"].include?(t1.custom_lock_version_before_type_cast) end def test_readonly_attributes diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index a578e81844..4192d12ff4 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -63,14 +63,6 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/ruby rails/, logger.debugs.first) end - def test_ignore_binds_payload_with_nil_column - event = Struct.new(:duration, :payload) - - logger = TestDebugLogSubscriber.new - logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]])) - assert_equal 1, logger.debugs.length - end - def test_basic_query_logging Developer.all.load wait @@ -125,12 +117,5 @@ class LogSubscriberTest < ActiveRecord::TestCase wait assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) end - - def test_nil_binary_data_is_logged - binary = Binary.create(data: "") - binary.update_attributes(data: nil) - wait - assert_match(/<NULL binary data>/, @logger.logged(:debug).join) - end end end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index d774cfebc4..3cd4073a38 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -68,8 +68,8 @@ module ActiveRecord five = columns.detect { |c| c.name == "five" } unless mysql assert_equal "hello", one.default - assert_equal true, two.type_cast_from_database(two.default) - assert_equal false, three.type_cast_from_database(three.default) + assert_equal true, connection.lookup_cast_type_from_column(two).type_cast_from_database(two.default) + assert_equal false, connection.lookup_cast_type_from_column(three).type_cast_from_database(three.default) assert_equal '1', four.default assert_equal "hello", five.default unless mysql end @@ -82,7 +82,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array + assert array_column.array? end def test_create_table_with_array_column @@ -93,7 +93,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array + assert array_column.array? end end @@ -195,32 +195,29 @@ module ActiveRecord end def test_create_table_with_timestamps_should_create_datetime_columns - # FIXME: Remove the silence when we change the default `null` behavior - ActiveSupport::Deprecation.silence do - connection.create_table table_name do |t| - t.timestamps - end + connection.create_table table_name do |t| + t.timestamps end created_columns = connection.columns(table_name) created_at_column = created_columns.detect {|c| c.name == 'created_at' } updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } - assert created_at_column.null - assert updated_at_column.null + assert !created_at_column.null + assert !updated_at_column.null end def test_create_table_with_timestamps_should_create_datetime_columns_with_options connection.create_table table_name do |t| - t.timestamps :null => false + t.timestamps null: true end created_columns = connection.columns(table_name) created_at_column = created_columns.detect {|c| c.name == 'created_at' } updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } - assert !created_at_column.null - assert !updated_at_column.null + assert created_at_column.null + assert updated_at_column.null end def test_create_table_without_a_block @@ -406,6 +403,17 @@ module ActiveRecord end end + def test_drop_table_if_exists + connection.create_table(:testings) + assert connection.table_exists?(:testings) + connection.drop_table(:testings, if_exists: true) + assert_not connection.table_exists?(:testings) + end + + def test_drop_table_if_exists_nothing_raised + assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) } + end + private def testing_table_with_only_foo_attribute connection.create_table :testings, :id => false do |t| @@ -429,7 +437,7 @@ module ActiveRecord teardown do [:wagons, :trains].each do |table| - @connection.drop_table(table) if @connection.table_exists?(table) + @connection.drop_table table, if_exists: true end end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 62186e13a5..4637970ce0 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -3,7 +3,7 @@ require 'cases/helper' module ActiveRecord class Migration class ColumnPositioningTest < ActiveRecord::TestCase - attr_reader :connection, :table_name + attr_reader :connection alias :conn :connection def setup diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index e6aa901814..54d3a729c3 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -196,7 +196,7 @@ module ActiveRecord old_columns = connection.columns(TestModel.table_name) assert old_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' && c.type == :boolean && default == true } @@ -204,11 +204,11 @@ module ActiveRecord new_columns = connection.columns(TestModel.table_name) assert_not new_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == true } assert new_columns.find { |c| - default = c.type_cast_from_database(c.default) + default = connection.lookup_cast_type_from_column(c).type_cast_from_database(c.default) c.name == 'approved' and c.type == :boolean and default == false } change_column :test_models, :approved, :boolean, :default => true diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 51e21528c2..78d9dd90a3 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -29,8 +29,8 @@ module ActiveRecord teardown do if defined?(@connection) - @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts' - @connection.drop_table "rockets" if @connection.table_exists? 'rockets' + @connection.drop_table "astronauts", if_exists: true + @connection.drop_table "rockets", if_exists: true end end @@ -220,6 +220,18 @@ module ActiveRecord ensure silence_stream($stdout) { migration.migrate(:down) } end + + private + + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end end end end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index bba8897a0d..99de7db70c 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -10,8 +10,8 @@ module ActiveRecord end teardown do - @connection.execute("drop table if exists testings") - @connection.execute("drop table if exists testing_parents") + @connection.drop_table("testings") if @connection.table_exists? "testings" + @connection.drop_table("testing_parents") if @connection.table_exists? "testing_parents" end test "foreign keys can be created with the table" do diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index c8b3f75e10..3eef308428 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -39,33 +39,35 @@ module ActiveRecord end end - def test_rename_table - rename_table :test_models, :octopi + unless current_adapter?(:FbAdapter) # Firebird cannot rename tables + def test_rename_table + rename_table :test_models, :octopi - connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") - end + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + end - def test_rename_table_with_an_index - add_index :test_models, :url + def test_rename_table_with_an_index + add_index :test_models, :url - rename_table :test_models, :octopi + rename_table :test_models, :octopi - connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") - index = connection.indexes(:octopi).first - assert index.columns.include?("url") - assert_equal 'index_octopi_on_url', index.name - end + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + index = connection.indexes(:octopi).first + assert index.columns.include?("url") + assert_equal 'index_octopi_on_url', index.name + end - def test_rename_table_does_not_rename_custom_named_index - add_index :test_models, :url, name: 'special_url_idx' + def test_rename_table_does_not_rename_custom_named_index + add_index :test_models, :url, name: 'special_url_idx' - rename_table :test_models, :octopi + rename_table :test_models, :octopi - assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) + assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) + end end if current_adapter?(:PostgreSQLAdapter) @@ -84,8 +86,8 @@ module ActiveRecord assert connection.table_exists? :felines ensure disable_extension!('uuid-ossp', connection) - connection.drop_table :cats if connection.table_exists? :cats - connection.drop_table :felines if connection.table_exists? :felines + connection.drop_table :cats, if_exists: true + connection.drop_table :felines, if_exists: true end end end diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb index 8fd770abd1..24cba84a09 100644 --- a/activerecord/test/cases/migration/table_and_index_test.rb +++ b/activerecord/test/cases/migration/table_and_index_test.rb @@ -6,11 +6,11 @@ module ActiveRecord def test_add_schema_info_respects_prefix_and_suffix conn = ActiveRecord::Base.connection - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters ActiveRecord::Base.table_name_prefix = 'p_' ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) conn.initialize_schema_migrations_table diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 3192b797b4..6ea8b93be7 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -90,7 +90,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_migration_detection_without_schema_migration_table - ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations') + ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths @@ -162,6 +162,7 @@ class MigrationTest < ActiveRecord::TestCase assert !BigNumber.table_exists? GiveMeBigNumbers.up + BigNumber.reset_column_information assert BigNumber.create( :bank_balance => 1586.43, @@ -397,6 +398,7 @@ class MigrationTest < ActiveRecord::TestCase Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up + Thing.reset_column_information assert Thing.create("content" => "hello world") assert_equal "hello world", Thing.first.content @@ -416,6 +418,7 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_suffix = '_suffix' Reminder.reset_table_name Reminder.reset_sequence_name + Reminder.reset_column_information WeNeedReminders.up assert Reminder.create("content" => "hello world", "remind_at" => Time.now) assert_equal "hello world", Reminder.first.content @@ -936,4 +939,14 @@ class CopyMigrationsTest < ActiveRecord::TestCase end end end + + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end end diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index 14d4ef457d..4aaf6f8b5f 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -250,8 +250,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time - assert topic.bonus_time.utc? + assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time + assert_not topic.bonus_time.utc? end end end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 3831de6ae3..15c60d5562 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -93,6 +93,13 @@ class MultipleDbTest < ActiveRecord::TestCase assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection end + def test_count_on_custom_connection + ActiveRecord::Base.remove_connection + assert_equal 1, College.count + ensure + ActiveRecord::Base.establish_connection :arunit + end + unless in_memory_db? def test_associations_should_work_when_model_has_no_connection begin diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 5c7e8a65d2..198cd6f341 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -672,7 +672,7 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_not_assign_destroy_key_to_a_record - assert_nothing_raised ActiveRecord::UnknownAttributeError do + assert_nothing_raised ActiveModel::AttributeAssignment::UnknownAttributeError do @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }}) end end @@ -1037,4 +1037,21 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR ShipPart.create!(:ship => @ship, :name => "Stern") assert_no_queries { @ship.valid? } end + + test "circular references do not perform unnecessary queries" do + ship = Ship.new(name: "The Black Rock") + part = ship.parts.build(name: "Stern") + ship.treasures.build(looter: part) + + assert_queries 3 do + ship.save! + end + end + + test "nested singular associations are validated" do + part = ShipPart.new(name: "Stern", ship_attributes: { name: nil }) + + assert_not part.valid? + assert_equal ["Ship name can't be blank"], part.errors.full_messages + end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index d6816041bc..2803ad2de0 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -356,6 +356,16 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal("David", topic_reloaded.author_name) end + def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed + klass = Class.new(Topic) do + def self.name; 'Topic'; end + end + topic = klass.create(title: 'Another New Topic') + assert_queries(0) do + topic.update_attribute(:title, 'Another New Topic') + end + end + def test_delete topic = Topic.find(1) assert_equal topic, topic.delete, 'topic.delete did not return self' diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 751eccc015..4b668f84dd 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -210,7 +210,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase end teardown do - @connection.execute("DROP TABLE IF EXISTS barcodes") + @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes end def test_any_type_primary_key diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 1d6ae2f67f..ad09340518 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -125,14 +125,11 @@ module ActiveRecord end def test_crazy_object - crazy = Class.new.new - expected = "'#{YAML.dump(crazy)}'" - assert_equal expected, @quoter.quote(crazy, nil) - end - - def test_crazy_object_calls_quote_string - crazy = Class.new { def initialize; @lol = 'lo\l' end }.new - assert_match "lo\\\\l", @quoter.quote(crazy, nil) + crazy = Object.new + e = assert_raises(TypeError) do + @quoter.quote(crazy, nil) + end + assert_equal "can't quote Object", e.message end def test_quote_string_no_column diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index e86b892a0a..b0e40c7145 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -80,10 +80,21 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal :integer, @first.column_for_attribute("id").type end - def test_non_existent_columns_return_nil - assert_deprecated do - assert_nil @first.column_for_attribute("attribute_that_doesnt_exist") - end + def test_non_existent_columns_return_null_object + column = @first.column_for_attribute("attribute_that_doesnt_exist") + assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column + assert_equal "attribute_that_doesnt_exist", column.name + assert_equal nil, column.sql_type + assert_equal nil, column.type + end + + def test_non_existent_types_are_identity_types + type = @first.type_for_attribute("attribute_that_doesnt_exist") + object = Object.new + + assert_equal object, type.type_cast_from_database(object) + assert_equal object, type.type_cast_from_user(object) + assert_equal object, type.type_cast_for_database(object) end def test_reflection_klass_for_nested_class_name diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index eb76ef6328..0a2e874e4f 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -82,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase left = Post.where(title: "omg").where(comments_count: 1) right = Post.where(title: "wtf").where(title: "bbq") - expected = [left.bind_values[1]] + right.bind_values + expected = [left.bound_attributes[1]] + right.bound_attributes merged = left.merge(right) - assert_equal expected, merged.bind_values + assert_equal expected, merged.bound_attributes assert !merged.to_sql.include?("omg") assert merged.to_sql.include?("wtf") assert merged.to_sql.include?("bbq") end - def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] - - right = Post.where(id: 20) - left = Post.where(id: 10) - - merged = left.merge(right) - assert_equal binds, merged.bind_values - end - def test_merging_reorders_bind_params post = Post.first right = Post.where(id: 1) diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 2443f10269..45ead08bd5 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -81,7 +81,7 @@ module ActiveRecord assert_equal [], relation.extending_values end - (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method| + (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal :foo, relation.public_send("#{method}_value") @@ -90,7 +90,7 @@ module ActiveRecord test '#from!' do assert relation.from!('foo').equal?(relation) - assert_equal ['foo', nil], relation.from_value + assert_equal 'foo', relation.from_clause.value end test '#lock!' do @@ -136,12 +136,12 @@ module ActiveRecord end test 'test_merge!' do - assert relation.merge!(where: :foo).equal?(relation) - assert_equal [:foo], relation.where_values + assert relation.merge!(select: :foo).equal?(relation) + assert_equal [:foo], relation.select_values end test 'merge with a proc' do - assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values + assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values end test 'none!' do diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb new file mode 100644 index 0000000000..2006fc9611 --- /dev/null +++ b/activerecord/test/cases/relation/or_test.rb @@ -0,0 +1,84 @@ +require "cases/helper" +require 'models/post' + +module ActiveRecord + class OrTest < ActiveRecord::TestCase + fixtures :posts + + def test_or_with_relation + expected = Post.where('id = 1 or id = 2').to_a + assert_equal expected, Post.where('id = 1').or(Post.where('id = 2')).to_a + end + + def test_or_identity + expected = Post.where('id = 1').to_a + assert_equal expected, Post.where('id = 1').or(Post.where('id = 1')).to_a + end + + def test_or_with_null_left + expected = Post.where('id = 1').to_a + assert_equal expected, Post.none.or(Post.where('id = 1')).to_a + end + + def test_or_with_null_right + expected = Post.where('id = 1').to_a + assert_equal expected, Post.where('id = 1').or(Post.none).to_a + end + + def test_or_with_bind_params + assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a + end + + def test_or_with_null_both + expected = Post.none.to_a + assert_equal expected, Post.none.or(Post.none).to_a + end + + def test_or_without_left_where + expected = Post.all + assert_equal expected, Post.or(Post.where('id = 1')).to_a + end + + def test_or_without_right_where + expected = Post.all + assert_equal expected, Post.where('id = 1').or(Post.all).to_a + end + + def test_or_preserves_other_querying_methods + expected = Post.where('id = 1 or id = 2 or id = 3').order('body asc').to_a + partial = Post.order('body asc') + assert_equal expected, partial.where('id = 1').or(partial.where(:id => [2, 3])).to_a + assert_equal expected, Post.order('body asc').where('id = 1').or(Post.order('body asc').where(:id => [2, 3])).to_a + end + + def test_or_with_incompatible_relations + assert_raises ArgumentError do + Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a + end + 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] } + assert_equal expected, groups.having('COUNT(*) > 1').or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] } + end + + def test_or_with_named_scope + expected = Post.where("id = 1 or body LIKE '\%a\%'").to_a + assert_equal expected, Post.where('id = 1').or(Post.containing_the_letter_a) + end + + def test_or_inside_named_scope + expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order('id DESC').to_a + assert_equal expected, Post.order(id: :desc).typographically_interesting + end + + def test_or_on_loaded_relation + expected = Post.where('id = 1 or id = 2').to_a + p = Post.where('id = 1') + p.load + assert_equal p.loaded?, true + assert_equal expected, p.or(Post.where('id = 2')).to_a + end + end +end diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 0cc081fced..8f62014622 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -8,7 +8,7 @@ module ActiveRecord Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) - assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql + assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql ensure Topic.reset_column_information end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index 619055f1e7..27bbd80f79 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -11,22 +11,11 @@ module ActiveRecord @name = 'title' end - def test_not_eq + def test_not_inverts_where_clause relation = Post.where.not(title: 'hello') + expected_where_clause = Post.where(title: 'hello').where_clause.invert - assert_equal 1, relation.where_values.length - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - end - - def test_not_null - expected = Post.arel_table[@name].not_eq(nil) - relation = Post.where.not(title: nil) - assert_equal([expected], relation.where_values) + assert_equal expected_where_clause, relation.where_clause end def test_not_with_nil @@ -35,146 +24,81 @@ module ActiveRecord end end - def test_not_in - expected = Post.arel_table[@name].not_in(%w[hello goodbye]) - relation = Post.where.not(title: %w[hello goodbye]) - assert_equal([expected], relation.where_values) - end - def test_association_not_eq - expected = Comment.arel_table[@name].not_eq('hello') + expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)) relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) - assert_equal(expected.to_sql, relation.where_values.first.to_sql) + assert_equal(expected.to_sql, relation.where_clause.ast.to_sql) end def test_not_eq_with_preceding_where relation = Post.where(title: 'hello').where.not(title: 'world') + expected_where_clause = + Post.where(title: 'hello').where_clause + + Post.where(title: 'world').where_clause.invert - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'world', bind.last + assert_equal expected_where_clause, relation.where_clause end def test_not_eq_with_succeeding_where relation = Post.where.not(title: 'hello').where(title: 'world') + expected_where_clause = + Post.where(title: 'hello').where_clause.invert + + Post.where(title: 'world').where_clause - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'world', bind.last - end - - def test_not_eq_with_string_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not("title = 'hello'") - assert_equal([expected], relation.where_values) - end - - def test_not_eq_with_array_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not(['title = ?', 'hello']) - assert_equal([expected], relation.where_values) + assert_equal expected_where_clause, relation.where_clause end def test_chaining_multiple relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') + expected_where_clause = + Post.where(author_id: [1, 2]).where_clause.invert + + Post.where(title: 'ruby on rails').where_clause.invert - expected = Post.arel_table['author_id'].not_in([1, 2]) - assert_equal(expected, relation.where_values[0]) - - value = relation.where_values[1] - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'ruby on rails', bind.last + assert_equal expected_where_clause, relation.where_clause end def test_rewhere_with_one_condition relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') + expected = Post.where(title: 'alone') - assert_equal 1, relation.where_values.size - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'alone', bind.last + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_multiple_overwriting_conditions relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again') + expected = Post.where(title: 'alone', body: 'again') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last - - value = relation.where_values[1] - bind = relation.bind_values[1] - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'again', bind.last - end - - def assert_bound_ast value, table, type - assert_equal table, value.left - assert_kind_of type, value - assert_kind_of Arel::Nodes::BindParam, value.right + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_one_overwriting_condition_and_one_unrelated relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone') + expected = Post.where(body: 'world', title: 'alone') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'world', bind.last - - value = relation.where_values.second - bind = relation.bind_values.second - - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_range relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_upper_bound_range relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_lower_bound_range relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_range relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb new file mode 100644 index 0000000000..c20ed94d90 --- /dev/null +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -0,0 +1,182 @@ +require "cases/helper" + +class ActiveRecord::Relation + class WhereClauseTest < ActiveRecord::TestCase + test "+ combines two where clauses" do + first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]]) + combined = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [["id", 1], ["name", "Sean"]], + ) + + assert_equal combined, first_clause + second_clause + end + + test "+ is associative, but not commutative" do + a = WhereClause.new(["a"], ["bind a"]) + b = WhereClause.new(["b"], ["bind b"]) + c = WhereClause.new(["c"], ["bind c"]) + + assert_equal a + (b + c), (a + b) + c + assert_not_equal a + b, b + a + end + + test "an empty where clause is the identity value for +" do + clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + + assert_equal clause, clause + WhereClause.empty + end + + test "merge combines two where clauses" do + a = WhereClause.new([table["id"].eq(1)], []) + b = WhereClause.new([table["name"].eq("Sean")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + + assert_equal expected, a.merge(b) + end + + test "merge keeps the right side, when two equality clauses reference the same column" do + a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + b = WhereClause.new([table["name"].eq("Jim")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], []) + + assert_equal expected, a.merge(b) + end + + test "merge removes bind parameters matching overlapping equality clauses" do + a = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Sean")], + ) + b = WhereClause.new( + [table["name"].eq(bind_param)], + [attribute("name", "Jim")] + ) + expected = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Jim")], + ) + + assert_equal expected, a.merge(b) + end + + test "merge allows for columns with the same name from different tables" do + skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen" + # We might be able to change the implementation to remove conflicts by index, rather than column name + end + + test "a clause knows if it is empty" do + assert WhereClause.empty.empty? + assert_not WhereClause.new(["anything"], []).empty? + end + + test "invert cannot handle nil" do + where_clause = WhereClause.new([nil], []) + + assert_raises ArgumentError do + where_clause.invert + end + end + + test "invert replaces each part of the predicate with its inverse" do + random_object = Object.new + original = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["id"].eq(1), + "sql literal", + random_object + ], []) + expected = WhereClause.new([ + table["id"].not_in([1, 2, 3]), + table["id"].not_eq(1), + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")), + Arel::Nodes::Not.new(random_object) + ], []) + + assert_equal expected, original.invert + end + + test "accept removes binary predicates referencing a given column" do + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + table["age"].gteq(bind_param), + ], [ + attribute("name", "Sean"), + attribute("age", 30), + ]) + expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)]) + + assert_equal expected, where_clause.except("id", "name") + end + + test "ast groups its predicates with AND" do + predicates = [ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + ] + where_clause = WhereClause.new(predicates, []) + expected = Arel::Nodes::And.new(predicates) + + assert_equal expected, where_clause.ast + end + + test "ast wraps any SQL literals in parenthesis" do + random_object = Object.new + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + "foo = bar", + random_object, + ], []) + expected = Arel::Nodes::And.new([ + table["id"].in([1, 2, 3]), + Arel::Nodes::Grouping.new(Arel.sql("foo = bar")), + Arel::Nodes::Grouping.new(random_object), + ]) + + assert_equal expected, where_clause.ast + end + + test "ast removes any empty strings" do + where_clause = WhereClause.new([table["id"].in([1, 2, 3])], []) + where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], []) + + assert_equal where_clause.ast, where_clause_with_empty.ast + end + + test "or joins the two clauses using OR" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")]) + expected_ast = + Arel::Nodes::Grouping.new( + Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param)) + ) + expected_binds = where_clause.binds + other_clause.binds + + assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql + assert_equal expected_binds, where_clause.or(other_clause).binds + end + + test "or returns an empty where clause when either side is empty" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + + assert_equal WhereClause.empty, where_clause.or(WhereClause.empty) + assert_equal WhereClause.empty, WhereClause.empty.or(where_clause) + end + + private + + def table + Arel::Table.new("table") + end + + def bind_param + Arel::Nodes::BindParam.new + end + + def attribute(name, value) + ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new) + end + end +end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d675953da6..537937decd 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -1,13 +1,15 @@ require "cases/helper" -require 'models/author' -require 'models/price_estimate' -require 'models/treasure' -require 'models/post' -require 'models/comment' -require 'models/edge' -require 'models/topic' -require 'models/binary' -require 'models/vertex' +require "models/author" +require "models/binary" +require "models/cake_designer" +require "models/chef" +require "models/comment" +require "models/edge" +require "models/post" +require "models/price_estimate" +require "models/topic" +require "models/treasure" +require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase @@ -26,6 +28,24 @@ module ActiveRecord } end + def test_where_copies_bind_params_in_the_right_order + author = authors(:david) + posts = author.posts.where.not(id: 1) + joined = Post.where(id: posts, title: posts.first.title) + + assert_equal joined, [posts.first] + end + + def test_where_copies_arel_bind_params + chef = Chef.create! + CakeDesigner.create!(chef: chef) + + cake_designers = CakeDesigner.joins(:chef).where(chefs: { id: chef.id }) + chefs = Chef.where(employable: cake_designers) + + assert_equal [chef], chefs.to_a + end + def test_rewhere_on_root assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index f7cb471984..194faa9473 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -156,12 +156,12 @@ module ActiveRecord relation = Relation.new(FakeKlass, :b, nil) relation = relation.merge where: :lol, readonly: true - assert_equal [:lol], relation.where_values + assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause assert_equal true, relation.readonly_value end test 'merging an empty hash into a relation' do - assert_equal [], Relation.new(FakeKlass, :b, nil).merge({}).where_values + assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause end test 'merging a hash with unknown keys raises' do @@ -173,18 +173,12 @@ module ActiveRecord values = relation.values values[:where] = nil - assert_not_nil relation.where_values + assert_not_nil relation.where_clause end test 'relations can be created with a values hash' do - relation = Relation.new(FakeKlass, :b, nil, where: [:foo]) - assert_equal [:foo], relation.where_values - end - - test 'merging a single where value' do - relation = Relation.new(FakeKlass, :b, nil) - relation.merge!(where: :foo) - assert_equal [:foo], relation.where_values + relation = Relation.new(FakeKlass, :b, nil, select: [:foo]) + assert_equal [:foo], relation.select_values end test 'merging a hash interpolates conditions' do @@ -197,7 +191,7 @@ module ActiveRecord relation = Relation.new(klass, :b, nil) relation.merge!(where: ['foo = ?', 'bar']) - assert_equal ['foo = bar'], relation.where_values + assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause end def test_merging_readonly_false diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fb9258c038..5c5ab499a0 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -16,6 +16,7 @@ require 'models/engine' require 'models/tyre' require 'models/minivan' require 'models/aircraft' +require "models/possession" class RelationTest < ActiveRecord::TestCase @@ -39,15 +40,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first end - def test_bind_values - relation = Post.all - assert_equal [], relation.bind_values - - relation2 = relation.bind 'foo' - assert_equal %w{ foo }, relation2.bind_values - assert_equal [], relation.bind_values - end - def test_two_scopes_with_includes_should_not_drop_any_include # heat habtm cache car = Car.incl_engines.incl_tyres.first @@ -857,6 +849,12 @@ class RelationTest < ActiveRecord::TestCase assert ! fake.exists?(authors(:david).id) end + def test_exists_uses_existing_scope + post = authors(:david).posts.first + authors = Author.includes(:posts).where(name: "David", posts: { id: post.id }) + assert authors.exists?(authors(:david).id) + end + def test_last authors = Author.all assert_equal authors(:bob), authors.last @@ -1382,12 +1380,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal "id", Post.all.primary_key end - def test_disable_implicit_join_references_is_deprecated - assert_deprecated do - ActiveRecord::Base.disable_implicit_join_references = true - end - end - def test_ordering_with_extra_spaces assert_equal authors(:david), Author.order('id DESC , name DESC').last end @@ -1468,10 +1460,31 @@ class RelationTest < ActiveRecord::TestCase def test_doesnt_add_having_values_if_options_are_blank scope = Post.having('') - assert_equal [], scope.having_values + assert scope.having_clause.empty? scope = Post.having([]) - assert_equal [], scope.having_values + assert scope.having_clause.empty? + end + + def test_having_with_binds_for_both_where_and_having + post = Post.first + having_then_where = Post.having(id: post.id).where(title: post.title).group(:id) + where_then_having = Post.where(title: post.title).having(id: post.id).group(:id) + + assert_equal [post], having_then_where + assert_equal [post], where_then_having + end + + def test_multiple_where_and_having_clauses + post = Post.first + having_then_where = Post.having(id: post.id).where(title: post.title) + .having(id: post.id).where(title: post.title).group(:id) + + assert_equal [post], having_then_where + end + + def test_grouping_by_column_with_reserved_name + assert_equal [], Possession.select(:where).group(:where).to_a end def test_references_triggers_eager_loading @@ -1657,6 +1670,14 @@ class RelationTest < ActiveRecord::TestCase end end + test "relations with cached arel can't be mutated [internal API]" do + relation = Post.all + relation.count + + assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) } + assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") } + end + test "relations show the records in #inspect" do relation = Post.limit(2) assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect @@ -1742,14 +1763,13 @@ class RelationTest < ActiveRecord::TestCase end def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] + binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))] right = Post.where(id: 20) left = Post.where(id: 10) merged = left.merge(right) - assert_equal binds, merged.bind_values + assert_equal binds, merged.bound_attributes end def test_merging_reorders_bind_params diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index f477cf7d92..262e0abc22 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -7,17 +7,6 @@ class SanitizeTest < ActiveRecord::TestCase def setup end - def test_sanitize_sql_hash_handles_associations - quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") - quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name") - quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals") - expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}" - - assert_deprecated do - assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}}) - end - end - def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 57b7346503..c8ea702488 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -204,25 +204,25 @@ class SchemaDumperTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_schema_dump_should_add_default_value_for_mysql_text_field output = standard_dump - assert_match %r{t.text\s+"body",\s+limit: 65535,\s+null: false$}, output + assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output end def test_schema_dump_includes_length_for_mysql_binary_fields output = standard_dump - assert_match %r{t.binary\s+"var_binary",\s+limit: 255$}, output - assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output + assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output + assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output end def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump - assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output - assert_match %r{t.binary\s+"normal_blob",\s+limit: 65535$}, output - assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output - assert_match %r{t.binary\s+"long_blob",\s+limit: 4294967295$}, output - assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output - assert_match %r{t.text\s+"normal_text",\s+limit: 65535$}, output - assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output - assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output + assert_match %r{t\.binary\s+"tiny_blob",\s+limit: 255$}, output + assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output + assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output + assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output + assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output + assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output + assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output + assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output end def test_schema_dumps_index_type @@ -235,19 +235,19 @@ class SchemaDumperTest < ActiveRecord::TestCase if mysql_56? def test_schema_dump_includes_datetime_precision output = standard_dump - assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output + assert_match %r{t\.datetime\s+"written_on",\s+precision: 6$}, output end end def test_schema_dump_includes_decimal_options output = dump_all_table_schema([/^[^n]/]) - assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output + assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output end if current_adapter?(:PostgreSQLAdapter) def test_schema_dump_includes_bigint_default output = standard_dump - assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output + assert_match %r{t\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output end if ActiveRecord::Base.connection.supports_extensions? @@ -271,9 +271,11 @@ class SchemaDumperTest < ActiveRecord::TestCase output = standard_dump # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers if current_adapter?(:OracleAdapter) - assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output + assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 38}, output + elsif current_adapter?(:FbAdapter) + assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 18}, output else - assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output + assert_match %r{t\.decimal\s+"atoms_in_universe",\s+precision: 55}, output end end @@ -282,7 +284,7 @@ class SchemaDumperTest < ActiveRecord::TestCase match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) assert_not_nil(match, "goofy_string_id table not found") assert_match %r(id: false), match[1], "no table id not preserved" - assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved" + assert_match %r{t\.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved" end def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 0738df1b54..4137b20c4a 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -284,8 +284,8 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_merging merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where)) - assert merged.where_values.empty? - assert !merged.where(name: "Jon").where_values.empty? + assert merged.where_clause.empty? + assert !merged.where(name: "Jon").where_clause.empty? end def test_order_in_default_scope_should_not_prevail @@ -426,19 +426,19 @@ class DefaultScopingTest < ActiveRecord::TestCase test "additional conditions are ANDed with the default scope" do scope = DeveloperCalledJamis.where(name: "David") - assert_equal 2, scope.where_values.length + assert_equal 2, scope.where_clause.ast.children.length assert_equal [], scope.to_a end test "additional conditions in a scope are ANDed with the default scope" do scope = DeveloperCalledJamis.david - assert_equal 2, scope.where_values.length + assert_equal 2, scope.where_clause.ast.children.length assert_equal [], scope.to_a end test "a scope can remove the condition from the default scope" do scope = DeveloperCalledJamis.david2 - assert_equal 1, scope.where_values.length - assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id) + assert_equal 1, scope.where_clause.ast.children.length + assert_equal Developer.where(name: "David"), scope end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 41f3449828..8cd94ebcc2 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -380,8 +380,8 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_should_not_duplicates_where_values - where_values = Topic.where("1=1").scope_with_lambda.where_values - assert_equal ["1=1"], where_values + relation = Topic.where("1=1") + assert_equal relation.where_clause, relation.scope_with_lambda.where_clause end def test_chaining_with_duplicate_joins diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index d7bcbf6203..02b32abebf 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -184,7 +184,7 @@ class RelationScopingTest < ActiveRecord::TestCase rescue end - assert !Developer.all.where_values.include?("name = 'Jamis'") + assert_not Developer.all.to_sql.include?("name = 'Jamis'"), "scope was not restored" end def test_default_scope_filters_on_joins diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb new file mode 100644 index 0000000000..3f7455d12d --- /dev/null +++ b/activerecord/test/cases/secure_token_test.rb @@ -0,0 +1,25 @@ +require 'cases/helper' +require 'models/user' + +class SecureTokenTest < ActiveRecord::TestCase + setup do + @user = User.new + end + + def test_token_values_are_generated_for_specified_attributes_and_persisted_on_save + @user.save + assert_not_nil @user.token + assert_not_nil @user.auth_token + end + + def test_regenerating_the_secure_token + @user.save + old_token = @user.token + old_auth_token = @user.auth_token + @user.regenerate_token + @user.regenerate_auth_token + + assert_not_equal @user.token, old_token + assert_not_equal @user.auth_token, old_auth_token + end +end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index c261a5b1c0..e29f7462c8 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -22,12 +22,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase end end - def test_list_of_serialized_attributes - assert_deprecated do - assert_equal %w(content), Topic.serialized_attributes.keys - end - end - def test_serialized_attribute Topic.serialize("content", MyObject) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 0f5caa52e3..f185cda263 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -4,7 +4,6 @@ require 'models/pet' require 'models/topic' class TransactionCallbacksTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false fixtures :topics, :owners, :pets class ReplyWithCallbacks < ActiveRecord::Base @@ -200,21 +199,21 @@ class TransactionCallbacksTest < ActiveRecord::TestCase end def test_call_after_rollback_when_commit_fails - @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction) - begin - @first.class.connection.singleton_class.class_eval do - def commit_db_transaction; raise "boom!"; end - end + @first.after_commit_block { |r| r.history << :after_commit } + @first.after_rollback_block { |r| r.history << :after_rollback } - @first.after_commit_block{|r| r.history << :after_commit} - @first.after_rollback_block{|r| r.history << :after_rollback} + assert_raises RuntimeError do + @first.transaction do + tx = @first.class.connection.transaction_manager.current_transaction + def tx.commit + raise + end - assert !@first.save rescue nil - assert_equal [:after_rollback], @first.history - ensure - @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction) - @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction) + @first.save + end end + + assert_equal [:after_rollback], @first.history end def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint @@ -266,47 +265,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal 2, @first.rollbacks end - def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called - old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks - ActiveRecord::Base.raise_in_transactional_callbacks = false - - def @first.last_after_transaction_error=(e); @last_transaction_error = e; end - def @first.last_after_transaction_error; @last_transaction_error; end - @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";} - @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";} - - second = TopicWithCallbacks.find(3) - second.after_commit_block{|r| r.history << :after_commit} - second.after_rollback_block{|r| r.history << :after_rollback} - - Topic.transaction do - @first.save! - second.save! - end - assert_equal :commit, @first.last_after_transaction_error - assert_equal [:after_commit], second.history - - second.history.clear - Topic.transaction do - @first.save! - second.save! - raise ActiveRecord::Rollback - end - assert_equal :rollback, @first.last_after_transaction_error - assert_equal [:after_rollback], second.history - ensure - ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config - end - - def test_after_commit_should_not_raise_when_raise_in_transactional_callbacks_false - old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks - ActiveRecord::Base.raise_in_transactional_callbacks = false - @first.after_commit_block{ fail "boom" } - Topic.transaction { @first.save! } - ensure - ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config - end - def test_after_commit_callback_should_not_swallow_errors @first.after_commit_block{ fail "boom" } assert_raises(RuntimeError) do diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0bbb4bb79e..d1d8e71c34 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -194,6 +194,16 @@ class TransactionTest < ActiveRecord::TestCase assert_equal posts_count, author.posts(true).size end + def test_cancellation_from_returning_false_in_before_filter + def @first.before_save_for_transaction + false + end + + assert_deprecated do + @first.save + end + end + def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic @first nbooks_before_destroy = Book.count @@ -493,35 +503,32 @@ class TransactionTest < ActiveRecord::TestCase assert topic.frozen?, 'not frozen' end - # The behavior of killed threads having a status of "aborting" was changed - # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction - # and there's nothing we can do about it. - if !RUBY_VERSION.start_with?('1.9') && !in_memory_db? - def test_rollback_when_thread_killed - queue = Queue.new - thread = Thread.new do - Topic.transaction do - @first.approved = true - @second.approved = false - @first.save + def test_rollback_when_thread_killed + return if in_memory_db? - queue.push nil - sleep + queue = Queue.new + thread = Thread.new do + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save - @second.save - end + queue.push nil + sleep + + @second.save end + end - queue.pop - thread.kill - thread.join + queue.pop + thread.kill + thread.join - assert @first.approved?, "First should still be changed in the objects" - assert !@second.approved?, "Second should still be changed in the objects" + assert @first.approved?, "First should still be changed in the objects" + assert !@second.approved?, "Second should still be changed in the objects" - assert !Topic.find(1).approved?, "First shouldn't have been approved" - assert Topic.find(2).approved?, "Second should still be approved" - end + assert !Topic.find(1).approved?, "First shouldn't have been approved" + assert Topic.find(2).approved?, "Second should still be approved" end def test_restore_active_record_state_for_all_records_in_a_transaction @@ -643,6 +650,27 @@ class TransactionTest < ActiveRecord::TestCase assert transaction.state.committed? end + def test_transaction_rollback_with_primarykeyless_tables + connection = ActiveRecord::Base.connection + connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t| + t.integer :thing_id + end + + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'transaction_without_primary_keys' + after_commit { } # necessary to trigger the has_transactional_callbacks branch + end + + assert_no_difference(-> { klass.count }) do + ActiveRecord::Base.transaction do + klass.create! + raise ActiveRecord::Rollback + end + end + ensure + connection.execute("DROP TABLE IF EXISTS transaction_without_primary_keys") + end + private %w(validation save destroy).each do |filter| @@ -650,7 +678,7 @@ class TransactionTest < ActiveRecord::TestCase meta = class << topic; self; end meta.send("define_method", "before_#{filter}_for_transaction") do Book.create - false + throw(:abort) end end end diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index ff956b7680..0c60f0690c 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -60,55 +60,68 @@ module ActiveRecord test "values below int min value are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("-2147483649") + Integer.new.type_cast_for_database(-2147483649) end end test "values above int max value are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("2147483648") + Integer.new.type_cast_for_database(2147483648) end end test "very small numbers are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("-9999999999999999999999999999999") + Integer.new.type_cast_for_database(-9999999999999999999999999999999) end end test "very large numbers are out of range" do assert_raises(::RangeError) do - Integer.new.type_cast_from_user("9999999999999999999999999999999") + Integer.new.type_cast_for_database(9999999999999999999999999999999) end end test "normal numbers are in range" do type = Integer.new - assert_equal(0, type.type_cast_from_user("0")) - assert_equal(-1, type.type_cast_from_user("-1")) - assert_equal(1, type.type_cast_from_user("1")) + assert_equal(0, type.type_cast_for_database(0)) + assert_equal(-1, type.type_cast_for_database(-1)) + assert_equal(1, type.type_cast_for_database(1)) end test "int max value is in range" do - assert_equal(2147483647, Integer.new.type_cast_from_user("2147483647")) + assert_equal(2147483647, Integer.new.type_cast_for_database(2147483647)) end test "int min value is in range" do - assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648")) + assert_equal(-2147483648, Integer.new.type_cast_for_database(-2147483648)) end test "columns with a larger limit have larger ranges" do type = Integer.new(limit: 8) - assert_equal(9223372036854775807, type.type_cast_from_user("9223372036854775807")) - assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808")) + assert_equal(9223372036854775807, type.type_cast_for_database(9223372036854775807)) + assert_equal(-9223372036854775808, type.type_cast_for_database(-9223372036854775808)) assert_raises(::RangeError) do - type.type_cast_from_user("-9999999999999999999999999999999") + type.type_cast_for_database(-9999999999999999999999999999999) end assert_raises(::RangeError) do - type.type_cast_from_user("9999999999999999999999999999999") + type.type_cast_for_database(9999999999999999999999999999999) end end + + test "values which are out of range can be re-assigned" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'posts' + attribute :foo, Type::Integer.new + end + model = klass.new + + model.foo = 2147483648 + model.foo = 1 + + assert_equal 1, model.foo + end end end end diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb index b6f673109e..4b8e2fb518 100644 --- a/activerecord/test/cases/type/unsigned_integer_test.rb +++ b/activerecord/test/cases/type/unsigned_integer_test.rb @@ -4,12 +4,12 @@ module ActiveRecord module Type class UnsignedIntegerTest < ActiveRecord::TestCase test "unsigned int max value is in range" do - assert_equal(4294967295, UnsignedInteger.new.type_cast_from_user("4294967295")) + assert_equal(4294967295, UnsignedInteger.new.type_cast_for_database(4294967295)) end test "minus value is out of range" do assert_raises(::RangeError) do - UnsignedInteger.new.type_cast_from_user("-1") + UnsignedInteger.new.type_cast_for_database(-1) end end end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index b0979cbe1f..d35d34ff2d 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -17,6 +17,10 @@ module ActiveRecord assert type.type_cast_from_user('TRUE') assert type.type_cast_from_user('on') assert type.type_cast_from_user('ON') + assert type.type_cast_from_user(' ') + assert type.type_cast_from_user("\u3000\r\n") + assert type.type_cast_from_user("\u0000") + assert type.type_cast_from_user('SOMETHING RANDOM') # explicitly check for false vs nil assert_equal false, type.type_cast_from_user(false) @@ -28,12 +32,6 @@ module ActiveRecord assert_equal false, type.type_cast_from_user('FALSE') assert_equal false, type.type_cast_from_user('off') assert_equal false, type.type_cast_from_user('OFF') - assert_deprecated do - assert_equal false, type.type_cast_from_user(' ') - assert_equal false, type.type_cast_from_user("\u3000\r\n") - assert_equal false, type.type_cast_from_user("\u0000") - assert_equal false, type.type_cast_from_user('SOMETHING RANDOM') - end end def test_type_cast_float @@ -119,6 +117,23 @@ module ActiveRecord assert_equal Encoding::ASCII_8BIT, type_cast.encoding end end + + def test_attributes_which_are_invalid_for_database_can_still_be_reassigned + type_which_cannot_go_to_the_database = Type::Value.new + def type_which_cannot_go_to_the_database.type_cast_for_database(*) + raise + end + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'posts' + attribute :foo, type_which_cannot_go_to_the_database + end + model = klass.new + + model.foo = "foo" + model.foo = "bar" + + assert_equal "bar", model.foo + end end end end diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index c34e7d5a30..b30b50f597 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -19,7 +19,7 @@ class XmlSerializationTest < ActiveRecord::TestCase def test_should_serialize_default_root_with_namespace @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact" - assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml + assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml assert_match %r{</contact>$}, @xml end |