diff options
14 files changed, 175 insertions, 139 deletions
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index e676c837b4..18cd205bad 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -11,7 +11,7 @@ module ActionDispatch class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] - class Constraints < Endpoint #:nodoc: + class Constraints < Routing::Endpoint #:nodoc: attr_reader :app, :constraints SERVE = ->(app, req) { app.serve req } diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3724b1a387..79faa9326d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix `rake db:structure:dump` on Postgres when multiple schemas are used. + + Fixes #22346. + + *Nick Muerdter*, *ckoenig* + * Add schema dumping support for PostgreSQL geometric data types. *Ryuta Kamizono* @@ -211,9 +217,9 @@ Example: - config.generators do |g| - g.orm :active_record, primary_key_type: :uuid - end + config.generators do |g| + g.orm :active_record, primary_key_type: :uuid + end *Jon McCartie* @@ -289,10 +295,10 @@ To load the fixtures file `accounts.yml` as the `User` model, use: - _fixture: - model_class: User - david: - name: David + _fixture: + model_class: User + david: + name: David Fixes #9516. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4d4dc07b04..4b6912c616 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -95,14 +95,15 @@ module ActiveRecord attr_reader :prepared_statements - def initialize(connection, logger = nil, pool = nil) #:nodoc: + def initialize(connection, logger = nil, config = {}) # :nodoc: super() @connection = connection @owner = nil @instrumenter = ActiveSupport::Notifications.instrumenter @logger = logger - @pool = pool + @config = config + @pool = nil @schema_cache = SchemaCache.new self @visitor = nil @prepared_statements = false 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 023e06800e..25ba42e5c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -143,8 +143,7 @@ module ActiveRecord # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) - super(connection, logger) - @config = config + super(connection, logger, config) @quoted_column_names, @quoted_table_names = {}, {} @visitor = Arel::Visitors::MySQL.new self diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f731da9e18..aa43854d01 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -192,7 +192,7 @@ module ActiveRecord # Initializes and connects a PostgreSQL adapter. def initialize(connection, logger, connection_parameters, config) - super(connection, logger) + super(connection, logger, config) @visitor = Arel::Visitors::PostgreSQL.new self if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @@ -202,7 +202,7 @@ module ActiveRecord @prepared_statements = false end - @connection_parameters, @config = connection_parameters, config + @connection_parameters = connection_parameters # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 90df9b8825..72ca909b02 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -78,11 +78,10 @@ module ActiveRecord end def initialize(connection, logger, connection_options, config) - super(connection, logger) + super(connection, logger, config) @active = nil @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) - @config = config @visitor = Arel::Visitors::SQLite.new self @quoted_column_names = {} diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index cd7d949239..8b4874044c 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -56,9 +56,9 @@ module ActiveRecord args = ['-s', '-x', '-O', '-f', filename] unless search_path.blank? - args << search_path.split(',').map do |part| + args += search_path.split(',').map do |part| "--schema=#{part.strip}" - end.join(' ') + end end args << configuration['database'] run_cmd('pg_dump', args, 'dumping') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 6686ce012d..91214da048 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -706,96 +706,13 @@ class FinderTest < ActiveRecord::TestCase assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first end - def test_bind_arity - assert_nothing_raised { bind '' } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } - - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } - assert_nothing_raised { bind '?', 1 } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } - end - def test_named_bind_variables - assert_equal '1', bind(':a', :a => 1) # ' ruby-mode - assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode - - assert_nothing_raised { bind("'+00:00'", :foo => "bar") } - assert_kind_of Firm, Company.where(["name = :name", { name: "37signals" }]).first assert_nil Company.where(["name = :name", { name: "37signals!" }]).first assert_nil Company.where(["name = :name", { name: "37signals!' OR 1=1" }]).first assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on end - def test_named_bind_arity - assert_nothing_raised { bind "name = :name", { name: "37signals" } } - assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } } - assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } } - end - - class SimpleEnumerable - include Enumerable - - def initialize(ary) - @ary = ary - end - - def each(&b) - @ary.each(&b) - end - end - - def test_bind_enumerable - quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) - - assert_equal '1,2,3', bind('?', [1, 2, 3]) - assert_equal quoted_abc, bind('?', %w(a b c)) - - assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) - assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' - - assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3])) - assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c))) - - assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3])) - assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # ' - end - - def test_bind_empty_enumerable - quoted_nil = ActiveRecord::Base.connection.quote(nil) - assert_equal quoted_nil, bind('?', []) - assert_equal " in (#{quoted_nil})", bind(' in (?)', []) - assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) - end - - def test_bind_empty_string - quoted_empty = ActiveRecord::Base.connection.quote('') - assert_equal quoted_empty, bind('?', '') - end - - def test_bind_chars - quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") - quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi") - assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars) - assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars) - end - - def test_bind_record - o = Struct.new(:quoted_id).new(1) - assert_equal '1', bind('?', o) - - os = [o] * 3 - assert_equal '1,1,1', bind('?', os) - end - - def test_named_bind_with_postgresql_type_casts - l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } - assert_nothing_raised(&l) - assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call - end - def test_string_sanitation assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") @@ -1136,14 +1053,6 @@ class FinderTest < ActiveRecord::TestCase end protected - def bind(statement, *vars) - if vars.first.is_a?(Hash) - ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first) - else - ActiveRecord::Base.send(:replace_bind_variables, statement, vars) - end - end - def table_with_custom_primary_key yield(Class.new(Toy) do def self.name diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 14e392ac30..07970fb1c1 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -69,4 +69,98 @@ class SanitizeTest < ActiveRecord::TestCase searchable_post.search("20% _reduction_!").to_a end end + + def test_bind_arity + assert_nothing_raised { bind '' } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '', 1 } + + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?' } + assert_nothing_raised { bind '?', 1 } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 } + end + + def test_named_bind_variables + assert_equal '1', bind(':a', :a => 1) # ' ruby-mode + assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode + + assert_nothing_raised { bind("'+00:00'", :foo => "bar") } + end + + def test_named_bind_arity + assert_nothing_raised { bind "name = :name", { name: "37signals" } } + assert_nothing_raised { bind "name = :name", { name: "37signals", id: 1 } } + assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", { id: 1 } } + end + + class SimpleEnumerable + include Enumerable + + def initialize(ary) + @ary = ary + end + + def each(&b) + @ary.each(&b) + end + end + + def test_bind_enumerable + quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) + + assert_equal '1,2,3', bind('?', [1, 2, 3]) + assert_equal quoted_abc, bind('?', %w(a b c)) + + assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) + assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' + + assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c))) + + assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # ' + end + + def test_bind_empty_enumerable + quoted_nil = ActiveRecord::Base.connection.quote(nil) + assert_equal quoted_nil, bind('?', []) + assert_equal " in (#{quoted_nil})", bind(' in (?)', []) + assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) + end + + def test_bind_empty_string + quoted_empty = ActiveRecord::Base.connection.quote('') + assert_equal quoted_empty, bind('?', '') + end + + def test_bind_chars + quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") + quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") + assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi") + assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper") + assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars) + assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars) + end + + def test_bind_record + o = Struct.new(:quoted_id).new(1) + assert_equal '1', bind('?', o) + + os = [o] * 3 + assert_equal '1,1,1', bind('?', os) + end + + def test_named_bind_with_postgresql_type_casts + l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') } + assert_nothing_raised(&l) + assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call + end + + private + def bind(statement, *vars) + if vars.first.is_a?(Hash) + ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first) + else + ActiveRecord::Base.send(:replace_bind_variables, statement, vars) + end + end end diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index c31f94b2f2..ba53f340ae 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -212,7 +212,7 @@ module ActiveRecord def test_structure_dump_with_schema_search_path @configuration['schema_search_path'] = 'foo,bar' - Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', 'my-app-db').returns(true) + Kernel.expects(:system).with('pg_dump', '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', 'my-app-db').returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) end @@ -228,7 +228,7 @@ module ActiveRecord end def test_structure_dump_with_dump_schemas_string - Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', "my-app-db").returns(true) + Kernel.expects(:system).with("pg_dump", '-s', '-x', '-O', '-f', @filename, '--schema=foo', '--schema=bar', "my-app-db").returns(true) with_dump_schemas('foo,bar') do ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 4b0ad37586..36f1ba2713 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -115,11 +115,10 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:increment, name, :amount => amount) do - @data.incr(normalize_key(name, options), amount) + rescue_error_with nil do + @data.incr(normalize_key(name, options), amount) + end end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil end # Decrement a cached value. This method uses the memcached decr atomic @@ -129,20 +128,16 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:decrement, name, :amount => amount) do - @data.decr(normalize_key(name, options), amount) + rescue_error_with nil do + @data.decr(normalize_key(name, options), amount) + end end - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil end # Clear the entire cache on all memcached servers. This method should # be used with care when shared cache is being used. def clear(options = nil) - @data.flush_all - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil + rescue_error_with(nil) { @data.flush_all } end # Get the statistics from the memcached servers. @@ -153,10 +148,7 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(key, options)) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - nil + rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } end # Write an entry to the cache. @@ -168,18 +160,14 @@ module ActiveSupport # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end - @data.send(method, key, value, expires_in, options) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false + rescue_error_with false do + @data.send(method, key, value, expires_in, options) + end end # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - @data.delete(key) - rescue Dalli::DalliError => e - logger.error("DalliError (#{e}): #{e.message}") if logger - false + rescue_error_with(false) { @data.delete(key) } end private @@ -207,10 +195,15 @@ module ActiveSupport if raw_value entry = Marshal.load(raw_value) rescue raw_value entry.is_a?(Entry) ? entry : Entry.new(entry) - else - nil end end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + fallback + end end end end diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index fe42cec158..ec31385077 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -777,7 +777,36 @@ Topic.create(title: nil).valid? # => true As you've already seen, the `:message` option lets you specify the message that will be added to the `errors` collection when validation fails. When this option is not used, Active Record will use the respective default error message -for each validation helper. +for each validation helper. The `:message` option accepts a `String` or `Proc`. + +A `String` `:message` value can optionally contain any/all of `%{value}`, +`%{attribute}`, and `%{model}` which will be dynamically replaced when +validation fails. + +A `Proc` `:message` value is given two arguments: a message key for i18n, and +a hash with `:model`, `:attribute`, and `:value` key-value pairs. + +```ruby +class Person < ActiveRecord::Base + # Hard-coded message + validates :name, presence: { message: "must be given please" } + + # Message with dynamic attribute value. %{value} will be replaced with + # the actual value of the attribute. %{attribute} and %{model} also + # available. + validates :age, numericality: { message: "%{value} seems wrong" } + + # Proc + validates :username, + uniqueness: { + # key = "activerecord.errors.models.person.attributes.username.taken" + # data = { model: "Person", attribute: "Username", value: <username> } + message: ->(key, data) do + "#{data[:value]} taken! Try again #{Time.zone.tomorrow}" + end + } +end +``` ### `:on` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 15cf087ddf..d20ec75b61 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Allow use of minitest-rails gem with Rails test runner. + + Fixes #22455. + + *Chris Kottom* + * Add `bin/test` script to rails plugin. `bin/test` can use the same API as `bin/rails test`. diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 959c83b27c..4e1fb13009 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -14,7 +14,7 @@ module Minitest SummaryReporter.prepend AggregatedResultSuppresion def self.plugin_rails_options(opts, options) - executable = Rails::TestUnitReporter.executable + executable = ::Rails::TestUnitReporter.executable opts.separator "" opts.separator "Usage: #{executable} [options] [files or directories]" opts.separator "You can run a single test by appending a line number to a filename:" |