diff options
Diffstat (limited to 'activerecord')
34 files changed, 345 insertions, 91 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 2af48f99db..56a8796318 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,21 @@ +* Preserve user supplied joins order as much as possible. + + Fixes #36761, #34328, #24281, #12953. + + *Ryuta Kamizono* + +* Allow `matches_regex` and `does_not_match_regexp` on the MySQL Arel visitor. + + *James Pearson* + +* Allow specifying fixtures to be ignored by setting `ignore` in YAML file's '_fixture' section. + + *Tongfei Gao* + +* Make the DATABASE_URL env variable only affect the primary connection. Add new env variables for multiple databases. + + *John Crepezzi*, *Eileen Uchitelle* + * Add a warning for enum elements with 'not_' prefix. class Foo @@ -56,5 +74,4 @@ *Michael Duchemin* - Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 6ad4c75fb5..2072f93194 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -62,7 +62,7 @@ module ActiveRecord::Associations::Builder # :nodoc: def middle_reflection(join_model) middle_name = [lhs_model.name.downcase.pluralize, - association_name].join("_").gsub("::", "_").to_sym + association_name.to_s].sort.join("_").gsub("::", "_").to_sym middle_options = middle_options join_model HasMany.create_reflection(lhs_model, diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0db0ad8595..404a7c02ba 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -100,7 +100,7 @@ module ActiveRecord # converting them into an array and iterating through them using # Array#select. # - # person.pets.select { |pet| pet.name =~ /oo/ } + # person.pets.select { |pet| /oo/.match?(pet.name) } # # => [ # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index 0cf67644af..5a21e36cc7 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -17,7 +17,7 @@ module ActiveRecord when false, nil then false else if !type_for_attribute(attr_name) { false } - if Numeric === value || value !~ /[^0-9]/ + if Numeric === value || !value.match?(/[^0-9]/) !value.to_i.zero? else return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index ef1eef6b69..0fe16270ed 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -572,7 +572,8 @@ module ActiveRecord end end - # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html + # See https://dev.mysql.com/doc/refman/5.7/en/server-error-reference.html + ER_DB_CREATE_EXISTS = 1007 ER_DUP_ENTRY = 1062 ER_NOT_NULL_VIOLATION = 1048 ER_NO_REFERENCED_ROW = 1216 @@ -592,6 +593,8 @@ module ActiveRecord def translate_exception(exception, message:, sql:, binds:) case error_number(exception) + when ER_DB_CREATE_EXISTS + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) when ER_DUP_ENTRY RecordNotUnique.new(message, sql: sql, binds: binds) when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2 diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 0732b69f81..20041f0c85 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -275,7 +275,7 @@ module ActiveRecord # hash and merges with the rest of the hash. # Connection details inside of the "url" key win any merge conflicts def resolve_hash_connection(spec) - if spec["url"] && spec["url"] !~ /^jdbc:/ + if spec["url"] && !spec["url"].match?(/^jdbc:/) connection_hash = resolve_url_connection(spec.delete("url")) spec.merge!(connection_hash) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0a7c6d8ac4..f33ba87435 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -467,6 +467,7 @@ module ActiveRecord UNIQUE_VIOLATION = "23505" SERIALIZATION_FAILURE = "40001" DEADLOCK_DETECTED = "40P01" + DUPLICATE_DATABASE = "42P04" LOCK_NOT_AVAILABLE = "55P03" QUERY_CANCELED = "57014" @@ -488,6 +489,8 @@ module ActiveRecord SerializationFailure.new(message, sql: sql, binds: binds) when DEADLOCK_DETECTED Deadlocked.new(message, sql: sql, binds: binds) + when DUPLICATE_DATABASE + DatabaseAlreadyExists.new(message, sql: sql, binds: binds) when LOCK_NOT_AVAILABLE LockWaitTimeout.new(message, sql: sql, binds: binds) when QUERY_CANCELED diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb index 8baa0f5af6..fcf13a910c 100644 --- a/activerecord/lib/active_record/database_configurations.rb +++ b/activerecord/lib/active_record/database_configurations.rb @@ -91,6 +91,19 @@ module ActiveRecord end alias :blank? :empty? + def each + throw_getter_deprecation(:each) + configurations.each { |config| + yield [config.env_name, config.config] + } + end + + def first + throw_getter_deprecation(:first) + config = configurations.first + [config.env_name, config.config] + end + private def env_with_configs(env = nil) if env @@ -168,17 +181,21 @@ module ActiveRecord end def environment_url_config(env, spec_name, config) - url = ENV["DATABASE_URL"] + url = environment_value_for(spec_name) return unless url ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, spec_name, url, config) end + def environment_value_for(spec_name) + spec_env_key = "#{spec_name.upcase}_DATABASE_URL" + url = ENV[spec_env_key] + url ||= ENV["DATABASE_URL"] if spec_name == "primary" + url + end + def method_missing(method, *args, &blk) case method - when :each, :first - throw_getter_deprecation(method) - configurations.send(method, *args, &blk) when :fetch throw_getter_deprecation(method) configs_for(env_name: args.first) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 20cc987d6e..509f21c9a5 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -187,6 +187,10 @@ module ActiveRecord class NoDatabaseError < StatementInvalid end + # Raised when creating a database if it exists. + class DatabaseAlreadyExists < StatementInvalid + end + # Raised when PostgreSQL returns 'cached plan must not change result type' and # we cannot retry gracefully (e.g. inside a transaction) class PreparedStatementCacheExpired < StatementInvalid @@ -334,7 +338,7 @@ module ActiveRecord # See the following: # # * https://www.postgresql.org/docs/current/static/transaction-iso.html - # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock + # * https://dev.mysql.com/doc/refman/5.7/en/server-error-reference.html#error_er_lock_deadlock class TransactionRollbackError < StatementInvalid end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index a86217abc0..ce209092f5 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -26,7 +26,7 @@ module ActiveRecord payload[:exception] || payload[:cached] || IGNORED_PAYLOADS.include?(payload[:name]) || - payload[:sql] !~ EXPLAINED_SQLS + !payload[:sql].match?(EXPLAINED_SQLS) end ActiveSupport::Notifications.subscribe("sql.active_record", new) diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb index f1ea0e022f..b2030a5bb9 100644 --- a/activerecord/lib/active_record/fixture_set/file.rb +++ b/activerecord/lib/active_record/fixture_set/file.rb @@ -29,6 +29,10 @@ module ActiveRecord config_row["model_class"] end + def ignored_fixtures + config_row["ignore"] + end + private def rows @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" } @@ -40,7 +44,7 @@ module ActiveRecord if row row.last else - { 'model_class': nil } + { 'model_class': nil, 'ignore': nil } end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 046ed0e95c..3df4b3c87f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -420,6 +420,29 @@ module ActiveRecord # # Any fixture labeled "DEFAULTS" is safely ignored. # + # Besides using "DEFAULTS", you can also specify what fixtures will + # be ignored by setting "ignore" in "_fixture" section. + # + # # users.yml + # _fixture: + # ignore: + # - base + # # or use "ignore: base" when there is only one fixture needs to be ignored. + # + # base: &base + # admin: false + # introduction: "This is a default description" + # + # admin: + # <<: *base + # admin: true + # + # visitor: + # <<: *base + # + # In the above example, 'base' will be ignored when creating fixtures. + # This can be used for common attributes inheriting. + # # == Configure the fixture model class # # It's possible to set the fixture's model class directly in the YAML file. @@ -614,7 +637,7 @@ module ActiveRecord end end - attr_reader :table_name, :name, :fixtures, :model_class, :config + attr_reader :table_name, :name, :fixtures, :model_class, :ignored_fixtures, :config def initialize(_, name, class_name, path, config = ActiveRecord::Base) @name = name @@ -647,8 +670,8 @@ module ActiveRecord # Returns a hash of rows to be inserted. The key is the table, the value is # a list of rows to insert to that table. def table_rows - # allow a standard key to be used for doing defaults in YAML - fixtures.delete("DEFAULTS") + # allow specifying fixtures to be ignored by setting `ignore` in `_fixture` section + fixtures.except!(*ignored_fixtures) TableRows.new( table_name, @@ -667,6 +690,21 @@ module ActiveRecord end end + def ignored_fixtures=(base) + @ignored_fixtures = + case base + when Array + base + when String + [base] + else + [] + end + + @ignored_fixtures << "DEFAULTS" unless @ignored_fixtures.include?("DEFAULTS") + @ignored_fixtures.compact + end + # Loads the fixtures from the YAML file at +path+. # If the file sets the +model_class+ and current instance value is not set, # it uses the file value. @@ -678,6 +716,7 @@ module ActiveRecord yaml_files.each_with_object({}) do |file, fixtures| FixtureSet::File.open(file) do |fh| self.model_class ||= fh.model_class if fh.model_class + self.ignored_fixtures ||= fh.ignored_fixtures fh.each do |fixture_name, row| fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 323b01ab2d..4dfe8655fe 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -566,10 +566,10 @@ module ActiveRecord def becomes(klass) became = klass.allocate became.send(:initialize) - became.instance_variable_set("@attributes", @attributes) - became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil) - became.instance_variable_set("@new_record", new_record?) - became.instance_variable_set("@destroyed", destroyed?) + became.instance_variable_set(:@attributes, @attributes) + became.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil) + became.instance_variable_set(:@new_record, new_record?) + became.instance_variable_set(:@destroyed, destroyed?) became.errors.copy!(errors) became end @@ -809,7 +809,7 @@ module ActiveRecord self.class.unscoped { self.class.find(id) } end - @attributes = fresh_object.instance_variable_get("@attributes") + @attributes = fresh_object.instance_variable_get(:@attributes) @new_record = false self end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 6957ba052b..29e2e2cedc 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1095,26 +1095,44 @@ module ActiveRecord end def build_left_outer_joins(manager, outer_joins, aliases) - buckets = { association_join: valid_association_list(outer_joins) } + buckets = Hash.new { |h, k| h[k] = [] } + buckets[:association_join] = valid_association_list(outer_joins) build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) end def build_joins(manager, joins, aliases) + buckets = Hash.new { |h, k| h[k] = [] } + unless left_outer_joins_values.empty? left_joins = valid_association_list(left_outer_joins_values.flatten) - joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin) + buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin) end - buckets = joins.group_by do |join| + joins.map! do |join| + if join.is_a?(String) + table.create_string_join(Arel.sql(join.strip)) unless join.blank? + else + join + end + end.compact_blank! + + while joins.first.is_a?(Arel::Nodes::Join) + join_node = joins.shift + if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty? + buckets[:join_node] << join_node + else + buckets[:leading_join] << join_node + end + end + + joins.each do |join| case join - when String - :string_join when Hash, Symbol, Array - :association_join + buckets[:association_join] << join when ActiveRecord::Associations::JoinDependency - :stashed_join + buckets[:stashed_join] << join when Arel::Nodes::Join - :join_node + buckets[:join_node] << join else raise "unknown class: %s" % join.class.name end @@ -1124,25 +1142,21 @@ module ActiveRecord end def build_join_query(manager, buckets, join_type, aliases) - buckets.default = [] - association_joins = buckets[:association_join] stashed_joins = buckets[:stashed_join] + leading_joins = buckets[:leading_join].tap(&:uniq!) join_nodes = buckets[:join_node].tap(&:uniq!) - string_joins = buckets[:string_join].compact_blank!.map!(&:strip).tap(&:uniq!) - - string_joins.map! { |join| table.create_string_join(Arel.sql(join)) } join_sources = manager.join_sources - join_sources.concat(join_nodes) unless join_nodes.empty? + join_sources.concat(leading_joins) unless leading_joins.empty? unless association_joins.empty? && stashed_joins.empty? - alias_tracker = alias_tracker(join_nodes + string_joins, aliases) + alias_tracker = alias_tracker(leading_joins + join_nodes, aliases) join_dependency = construct_join_dependency(association_joins, join_type) join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker)) end - join_sources.concat(string_joins) unless string_joins.empty? + join_sources.concat(join_nodes) unless join_nodes.empty? end def build_select(arel) diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 5d1ce19829..300e67b0aa 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -4,7 +4,6 @@ require "active_record/database_configurations" module ActiveRecord module Tasks # :nodoc: - class DatabaseAlreadyExists < StandardError; end # :nodoc: class DatabaseNotSupported < StandardError; end # :nodoc: # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index e3efeb75b5..b9a8ccb22c 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -15,12 +15,6 @@ module ActiveRecord establish_connection configuration_without_database connection.create_database configuration["database"], creation_options establish_connection configuration - rescue ActiveRecord::StatementInvalid => error - if connection.error_number(error.cause) == ER_DB_CREATE_EXISTS - raise DatabaseAlreadyExists - else - raise - end end def drop diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 626ffdfdf9..fc37db216d 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -21,12 +21,6 @@ module ActiveRecord connection.create_database configuration["database"], configuration.merge("encoding" => encoding) establish_connection configuration - rescue ActiveRecord::StatementInvalid => error - if error.cause.is_a?(PG::DuplicateDatabase) - raise DatabaseAlreadyExists - else - raise - end end def drop diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb index dd77cfdf66..6cb866715f 100644 --- a/activerecord/lib/arel/visitors/mysql.rb +++ b/activerecord/lib/arel/visitors/mysql.rb @@ -48,6 +48,14 @@ module Arel # :nodoc: all visit_Arel_Nodes_IsNotDistinctFrom o, collector end + def visit_Arel_Nodes_Regexp(o, collector) + infix_value o, collector, " REGEXP " + end + + def visit_Arel_Nodes_NotRegexp(o, collector) + infix_value o, collector, " NOT REGEXP " + end + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support # these, we must use a subquery. diff --git a/activerecord/lib/arel/visitors/oracle.rb b/activerecord/lib/arel/visitors/oracle.rb index aab66301ef..c54aec71a6 100644 --- a/activerecord/lib/arel/visitors/oracle.rb +++ b/activerecord/lib/arel/visitors/oracle.rb @@ -9,7 +9,7 @@ module Arel # :nodoc: all # if need to select first records without ORDER BY and GROUP BY and without DISTINCT # then can use simple ROWNUM in WHERE clause - if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/ + if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && !o.cores.first.set_quantifier.class.to_s.match?(/Distinct/) o.cores.last.wheres.push Nodes::LessThanOrEqual.new( Nodes::SqlLiteral.new("ROWNUM"), o.limit.expr ) diff --git a/activerecord/test/cases/arel/nodes/node_test.rb b/activerecord/test/cases/arel/nodes/node_test.rb index f1e0ce1ea9..8a9ecd84ca 100644 --- a/activerecord/test/cases/arel/nodes/node_test.rb +++ b/activerecord/test/cases/arel/nodes/node_test.rb @@ -14,7 +14,7 @@ module Arel }.grep(Class).each do |klass| next if Nodes::SqlLiteral == klass next if Nodes::BindParam == klass - next if klass.name =~ /^Arel::Nodes::(?:Test|.*Test$)/ + next if /^Arel::Nodes::(?:Test|.*Test$)/.match?(klass.name) assert klass.ancestors.include?(Nodes::Node), klass.name end end diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb index 526fe6787a..4512d8e8a6 100644 --- a/activerecord/test/cases/arel/select_manager_test.rb +++ b/activerecord/test/cases/arel/select_manager_test.rb @@ -514,7 +514,7 @@ module Arel assert_equal "bar", join.right end - it "should create join nodes with a outer join klass" do + it "should create join nodes with an outer join klass" do relation = Arel::SelectManager.new join = relation.create_join "foo", "bar", Arel::Nodes::OuterJoin assert_kind_of Arel::Nodes::OuterJoin, join diff --git a/activerecord/test/cases/arel/visitors/mysql_test.rb b/activerecord/test/cases/arel/visitors/mysql_test.rb index 5f37587957..05dccd126e 100644 --- a/activerecord/test/cases/arel/visitors/mysql_test.rb +++ b/activerecord/test/cases/arel/visitors/mysql_test.rb @@ -104,6 +104,52 @@ module Arel sql.must_be_like %{ NOT "users"."name" <=> NULL } end end + + describe "Nodes::Regexp" do + before do + @table = Table.new(:users) + @attr = @table[:id] + end + + it "should know how to visit" do + node = @table[:name].matches_regexp("foo.*") + node.must_be_kind_of Nodes::Regexp + compile(node).must_be_like %{ + "users"."name" REGEXP 'foo.*' + } + end + + it "can handle subqueries" do + subquery = @table.project(:id).where(@table[:name].matches_regexp("foo.*")) + node = @attr.in subquery + compile(node).must_be_like %{ + "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" REGEXP 'foo.*') + } + end + end + + describe "Nodes::NotRegexp" do + before do + @table = Table.new(:users) + @attr = @table[:id] + end + + it "should know how to visit" do + node = @table[:name].does_not_match_regexp("foo.*") + node.must_be_kind_of Nodes::NotRegexp + compile(node).must_be_like %{ + "users"."name" NOT REGEXP 'foo.*' + } + end + + it "can handle subqueries" do + subquery = @table.project(:id).where(@table[:name].does_not_match_regexp("foo.*")) + node = @attr.in subquery + compile(node).must_be_like %{ + "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" NOT REGEXP 'foo.*') + } + end + end end 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 3525fa2ab8..6bd305306f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -60,11 +60,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_belongs_to_does_not_use_order_by - ActiveRecord::SQLCounter.clear_log - Client.find(3).firm - ensure - sql_log = ActiveRecord::SQLCounter.log - assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}" + sql_log = capture_sql { Client.find(3).firm } + assert sql_log.all? { |sql| !/order by/i.match?(sql) }, "ORDER BY was used in the query: #{sql_log}" end def test_belongs_to_with_primary_key diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 25cfa0a723..de9742b250 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -700,10 +700,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal ["id"], developers(:david).projects.select(:id).first.attributes.keys end + def test_join_middle_table_alias + assert_equal( + 2, + Project.includes(:developers_projects).where.not("developers_projects.joined_on": nil).to_a.size + ) + end + def test_join_table_alias assert_equal( 3, - Developer.includes(projects: :developers).where.not("projects_developers_projects_join.joined_on": nil).to_a.size + Developer.includes(projects: :developers).where.not("developers_projects_projects_join.joined_on": nil).to_a.size ) end @@ -716,7 +723,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal( 3, - Developer.includes(projects: :developers).where.not("projects_developers_projects_join.joined_on": nil).group(group.join(",")).to_a.size + Developer.includes(projects: :developers).where.not("developers_projects_projects_join.joined_on": nil).group(group.join(",")).to_a.size ) end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index b1fcf47528..6c2a09c296 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -37,11 +37,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_has_one_does_not_use_order_by - ActiveRecord::SQLCounter.clear_log - companies(:first_firm).account - ensure - sql_log = ActiveRecord::SQLCounter.log - assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}" + sql_log = capture_sql { companies(:first_firm).account } + assert sql_log.all? { |sql| !/order by/i.match?(sql) }, "ORDER BY was used in the query: #{sql_log}" end def test_has_one_cache_nils diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index e0dac01f4a..b65af4b819 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -13,7 +13,7 @@ require "models/tag" class InnerJoinAssociationTest < ActiveRecord::TestCase fixtures :authors, :author_addresses, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, - :taggings, :tags + :taggings, :tags, :people def test_construct_finder_sql_applies_aliases_tables_on_association_conditions result = Author.joins(:thinking_posts, :welcome_posts).to_a @@ -36,16 +36,37 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_construct_finder_sql_does_not_table_name_collide_with_string_joins - sql = Person.joins(:agents).joins("JOIN people agents_people ON agents_people.primary_contact_id = people.id").to_sql - assert_match(/agents_people_2/i, sql) + string_join = <<~SQL + JOIN people agents_people ON agents_people.primary_contact_id = agents_people_2.id AND agents_people.id > agents_people_2.id + SQL + + expected = people(:susan) + assert_sql(/agents_people_2/i) do + assert_equal [expected], Person.joins(:agents).joins(string_join) + end end def test_construct_finder_sql_does_not_table_name_collide_with_aliased_joins - people = Person.arel_table - agents = people.alias("agents_people") - constraint = agents[:primary_contact_id].eq(people[:id]) - sql = Person.joins(:agents).joins(agents.create_join(agents, agents.create_on(constraint))).to_sql - assert_match(/agents_people_2/i, sql) + agents = Person.arel_table.alias("agents_people") + agents_2 = Person.arel_table.alias("agents_people_2") + constraint = agents[:primary_contact_id].eq(agents_2[:id]).and(agents[:id].gt(agents_2[:id])) + + expected = people(:susan) + assert_sql(/agents_people_2/i) do + assert_equal [expected], Person.joins(:agents).joins(agents.create_join(agents, agents.create_on(constraint))) + end + end + + def test_user_supplied_joins_order_should_be_preserved + string_join = <<~SQL + JOIN people agents_people_2 ON agents_people_2.primary_contact_id = people.id + SQL + agents = Person.arel_table.alias("agents_people") + agents_2 = Person.arel_table.alias("agents_people_2") + constraint = agents[:primary_contact_id].eq(agents_2[:id]).and(agents[:id].gt(agents_2[:id])) + + expected = people(:susan) + assert_equal [expected], Person.joins(string_join).joins(agents.create_join(agents, agents.create_on(constraint))) end def test_construct_finder_sql_ignores_empty_joins_hash 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 95e57f42e3..ee2972101f 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 @@ -334,6 +334,8 @@ module ActiveRecord } } + configs = ActiveRecord::DatabaseConfigurations.new(config) + actual = configs.configs_for(env_name: "default_env", spec_name: "primary").config expected = { "adapter" => "postgresql", "database" => "foo", @@ -341,11 +343,37 @@ module ActiveRecord "pool" => 5 } - ["primary", "animals"].each do |spec_name| - configs = ActiveRecord::DatabaseConfigurations.new(config) - actual = configs.configs_for(env_name: "default_env", spec_name: spec_name).config - assert_equal expected, actual - end + assert_equal expected, actual + + configs = ActiveRecord::DatabaseConfigurations.new(config) + actual = configs.configs_for(env_name: "default_env", spec_name: "animals").config + expected = { "pool" => 5 } + + assert_equal expected, actual + end + + def test_separate_database_env_vars + ENV["DATABASE_URL"] = "postgres://localhost/foo" + ENV["PRIMARY_DATABASE_URL"] = "postgres://localhost/primary" + ENV["ANIMALS_DATABASE_URL"] = "postgres://localhost/animals" + + config = { + "default_env" => { + "primary" => { "pool" => 5 }, + "animals" => { "pool" => 5 } + } + } + + configs = ActiveRecord::DatabaseConfigurations.new(config) + actual = configs.configs_for(env_name: "default_env", spec_name: "primary").config + assert_equal "primary", actual["database"] + + configs = ActiveRecord::DatabaseConfigurations.new(config) + actual = configs.configs_for(env_name: "default_env", spec_name: "animals").config + assert_equal "animals", actual["database"] + ensure + ENV.delete("PRIMARY_DATABASE_URL") + ENV.delete("ANIMALS_DATABASE_URL") end def test_does_not_change_other_environments diff --git a/activerecord/test/cases/database_configurations_test.rb b/activerecord/test/cases/database_configurations_test.rb index ed8151f01a..714b9d75c5 100644 --- a/activerecord/test/cases/database_configurations_test.rb +++ b/activerecord/test/cases/database_configurations_test.rb @@ -80,17 +80,20 @@ class LegacyDatabaseConfigurationsTest < ActiveRecord::TestCase def test_each_is_deprecated assert_deprecated do - ActiveRecord::Base.configurations.each do |db_config| - assert_equal "primary", db_config.spec_name + all_configs = ActiveRecord::Base.configurations.values + ActiveRecord::Base.configurations.each do |env_name, config| + assert_includes ["arunit", "arunit2", "arunit_without_prepared_statements"], env_name + assert_includes all_configs, config end end end def test_first_is_deprecated + first_config = ActiveRecord::Base.configurations.configurations.map(&:config).first assert_deprecated do - db_config = ActiveRecord::Base.configurations.first - assert_equal "arunit", db_config.env_name - assert_equal "primary", db_config.spec_name + env_name, config = ActiveRecord::Base.configurations.first + assert_equal "arunit", env_name + assert_equal first_config, config end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 8673a99c45..0ae156320a 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -580,7 +580,9 @@ class EnumTest < ActiveRecord::TestCase def self.name "Book" end - enum status: [:sent, :not_sent] + silence_warnings do + enum status: [:sent, :not_sent] + end end assert_match(expected_message, logger.logged(:warn).first) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index a7f01e898e..0861d938c5 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -67,7 +67,7 @@ class FixturesTest < ActiveRecord::TestCase end def call(_, _, _, _, values) - @events << values[:sql] if values[:sql] =~ /INSERT/ + @events << values[:sql] if /INSERT/.match?(values[:sql]) end end @@ -1279,6 +1279,33 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end end +class IgnoreFixturesTest < ActiveRecord::TestCase + fixtures :other_books, :parrots + + test "ignores books fixtures" do + assert_raise(StandardError) { other_books(:published) } + assert_raise(StandardError) { other_books(:published_paperback) } + assert_raise(StandardError) { other_books(:published_ebook) } + + assert_equal 2, Book.count + assert_equal "Agile Web Development with Rails", other_books(:awdr).name + assert_equal "published", other_books(:awdr).status + assert_equal "paperback", other_books(:awdr).format + assert_equal "english", other_books(:awdr).language + + assert_equal "Ruby for Rails", other_books(:rfr).name + assert_equal "ebook", other_books(:rfr).format + assert_equal "published", other_books(:rfr).status + end + + test "ignores parrots fixtures" do + assert_raise(StandardError) { parrots(:DEFAULT) } + assert_raise(StandardError) { parrots(:DEAD_PARROT) } + + assert_equal "DeadParrot", parrots(:polly).parrot_sti_class + end +end + class FixturesWithDefaultScopeTest < ActiveRecord::TestCase fixtures :bulbs diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index ac3c5bc26e..4b039395e8 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -93,11 +93,9 @@ if current_adapter?(:Mysql2Adapter) with_stubbed_connection_establish_connection do ActiveRecord::Base.connection.stub( :create_database, - proc { raise ActiveRecord::StatementInvalid } + proc { raise ActiveRecord::DatabaseAlreadyExists } ) do - ActiveRecord::Base.connection.stub(:error_number, 1007) do - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + ActiveRecord::Tasks::DatabaseTasks.create @configuration assert_equal "Database 'my-app-db' already exists\n", $stderr.string end diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index f9df650687..d74ba0580d 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -129,7 +129,7 @@ if current_adapter?(:PostgreSQLAdapter) with_stubbed_connection_establish_connection do ActiveRecord::Base.connection.stub( :create_database, - proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists } + proc { raise ActiveRecord::DatabaseAlreadyExists } ) do ActiveRecord::Tasks::DatabaseTasks.create @configuration diff --git a/activerecord/test/fixtures/other_books.yml b/activerecord/test/fixtures/other_books.yml new file mode 100644 index 0000000000..62806c03d7 --- /dev/null +++ b/activerecord/test/fixtures/other_books.yml @@ -0,0 +1,26 @@ +_fixture: + model_class: Book + ignore: + - PUBLISHED + - PUBLISHED_PAPERBACK + - PUBLISHED_EBOOK + +PUBLISHED: &PUBLISHED + status: :published + +PUBLISHED_PAPERBACK: &PUBLISHED_PAPERBACK + <<: *PUBLISHED + format: "paperback" + language: :english + +PUBLISHED_EBOOK: &PUBLISHED_EBOOK + <<: *PUBLISHED + format: "ebook" + +awdr: + <<: *PUBLISHED_PAPERBACK + name: "Agile Web Development with Rails" + +rfr: + <<: *PUBLISHED_EBOOK + name: "Ruby for Rails" diff --git a/activerecord/test/fixtures/parrots.yml b/activerecord/test/fixtures/parrots.yml index 8425ef98e0..4f0a090e03 100644 --- a/activerecord/test/fixtures/parrots.yml +++ b/activerecord/test/fixtures/parrots.yml @@ -1,3 +1,9 @@ +_fixture: + ignore: DEAD_PARROT + +DEAD_PARROT: &DEAD_PARROT + parrot_sti_class: DeadParrot + george: name: "Curious George" treasures: diamond, sapphire @@ -17,7 +23,7 @@ polly: name: $LABEL killer: blackbeard treasures: sapphire, ruby - parrot_sti_class: DeadParrot + <<: *DEAD_PARROT DEFAULTS: &DEFAULTS treasures: sapphire, ruby |