aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb23
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb4
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb2
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb41
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb47
-rw-r--r--actionview/lib/action_view/template/resolver.rb24
-rw-r--r--actionview/test/template/fallback_file_system_resolver_test.rb16
-rw-r--r--actionview/test/template/html_test.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb4
-rw-r--r--activerecord/lib/active_record/database_configurations.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb6
-rw-r--r--activerecord/lib/arel/visitors/ibm_db.rb3
-rw-r--r--activerecord/lib/arel/visitors/informix.rb3
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb5
-rw-r--r--activerecord/lib/arel/visitors/postgresql.rb14
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb105
-rw-r--r--activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb4
-rw-r--r--activesupport/CHANGELOG.md14
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb3
-rw-r--r--activesupport/lib/active_support/dependencies/zeitwerk_integration.rb6
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb2
26 files changed, 232 insertions, 123 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 5e7f3d2b74..6a8f9040d7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,7 +70,7 @@ PATH
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- zeitwerk (~> 1.3, >= 1.3.4)
+ zeitwerk (~> 1.4)
rails (6.0.0.beta3)
actioncable (= 6.0.0.beta3)
actionmailbox (= 6.0.0.beta3)
@@ -517,7 +517,7 @@ GEM
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (1.3.4)
+ zeitwerk (1.4.0)
PLATFORMS
java
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index c3e0ea3c89..962d10d81b 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -170,6 +170,7 @@ module Mime
def parse(accept_header)
if !accept_header.include?(",")
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
+ return [] unless accept_header
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
list, index = [], 0
@@ -221,7 +222,18 @@ module Mime
attr_reader :hash
+ MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
+ MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
+ MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
+ MIME_PARAMETER = "\s*\;\s+#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
+ MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?:\s*#{MIME_PARAMETER}\s*)*)\z/
+
+ class InvalidMimeType < StandardError; end
+
def initialize(string, symbol = nil, synonyms = [])
+ unless MIME_REGEXP.match?(string)
+ raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
+ end
@symbol, @synonyms = symbol, synonyms
@string = string
@hash = [@string, @synonyms, @symbol].hash
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 7a2fcd6db7..f0c869fba0 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -34,7 +34,28 @@ module ActionDispatch
end
def build(app)
- klass.new(app, *args, &block)
+ InstrumentationProxy.new(klass.new(app, *args, &block), inspect)
+ end
+ end
+
+ # This class is used to instrument the execution of a single middleware.
+ # It proxies the `call` method transparently and instruments the method
+ # call.
+ class InstrumentationProxy
+ EVENT_NAME = "process_middleware.action_dispatch"
+
+ def initialize(middleware, class_name)
+ @middleware = middleware
+
+ @payload = {
+ middleware: class_name,
+ }
+ end
+
+ def call(env)
+ ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
+ @middleware.call(env)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index d67044b4ac..da3ade652e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1440,6 +1440,9 @@ module ActionDispatch
# Allows you to specify the default value for optional +format+
# segment or disable it by supplying +false+.
#
+ # [:param]
+ # Allows you to override the default param name of +:id+ in the URL.
+ #
# === Examples
#
# # routes call <tt>Admin::PostsController</tt>
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index 9889f61951..6c65bec62f 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -38,8 +38,8 @@ module ActionDispatch
end
def self.parser(content_type)
- mime = Mime::Type.lookup(content_type)
- encoder(mime ? mime.ref : nil).response_parser
+ type = Mime::Type.lookup(content_type).ref if content_type
+ encoder(type).response_parser
end
def self.encoder(name)
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index 6de91c57b7..00b2798aeb 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -25,7 +25,7 @@ module ContentNegotiation
assert_body "Hello world text/html!"
end
- test "A js or */* Accept header on xhr will return HTML" do
+ test "A js or */* Accept header on xhr will return JavaScript" do
get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "text/javascript, */*" }, xhr: true
assert_body "Hello world text/javascript!"
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 5f43e5a3c5..90f2eccd19 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -3,13 +3,24 @@
require "abstract_unit"
class MiddlewareStackTest < ActiveSupport::TestCase
- class FooMiddleware; end
- class BarMiddleware; end
- class BazMiddleware; end
- class HiyaMiddleware; end
- class BlockMiddleware
+ class Base
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ end
+ end
+
+ class FooMiddleware < Base; end
+ class BarMiddleware < Base; end
+ class BazMiddleware < Base; end
+ class HiyaMiddleware < Base; end
+ class BlockMiddleware < Base
attr_reader :block
- def initialize(&block)
+ def initialize(app, &block)
+ super(app)
@block = block
end
end
@@ -109,6 +120,24 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal @stack.last, @stack.last
end
+ test "instruments the execution of middlewares" do
+ app = @stack.build(proc { |env| [200, {}, []] })
+ env = {}
+
+ events = []
+
+ subscriber = proc do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "process_middleware.action_dispatch") do
+ app.call(env)
+ end
+
+ assert_equal 2, events.count
+ assert_equal ["MiddlewareStackTest::BarMiddleware", "MiddlewareStackTest::FooMiddleware"], events.map { |e| e.payload[:middleware] }
+ end
+
test "includes a middleware" do
assert_equal true, @stack.include?(ActionDispatch::MiddlewareStack::Middleware.new(BarMiddleware, nil, nil))
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 45d91883c0..50f6c06fee 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -174,4 +174,51 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_not (Mime[:js] !~ "application/javascript")
assert Mime[:html] =~ "application/xhtml+xml"
end
+
+ test "can be initialized with wildcards" do
+ assert_equal "*/*", Mime::Type.new("*/*").to_s
+ assert_equal "text/*", Mime::Type.new("text/*").to_s
+ assert_equal "video/*", Mime::Type.new("video/*").to_s
+ end
+
+ test "can be initialized with parameters" do
+ assert_equal "text/html; parameter", Mime::Type.new("text/html; parameter").to_s
+ assert_equal "text/html; parameter=abc", Mime::Type.new("text/html; parameter=abc").to_s
+ assert_equal 'text/html; parameter="abc"', Mime::Type.new('text/html; parameter="abc"').to_s
+ assert_equal 'text/html; parameter=abc; parameter2="xyz"', Mime::Type.new('text/html; parameter=abc; parameter2="xyz"').to_s
+ end
+
+ test "invalid mime types raise error" do
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("too/many/slash")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("missingslash")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("improper/semicolon;")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new('improper/semicolon; parameter=abc; parameter2="xyz";')
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("text/html, text/plain")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("*/html")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new(nil)
+ end
+ end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 07c44307ff..1c577348e5 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -184,17 +184,21 @@ module ActionView
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
template_paths.map do |template|
- handler, format, variant = extract_handler_and_format_and_variant(template)
-
- FileTemplate.new(File.expand_path(template), handler,
- virtual_path: path.virtual,
- format: format,
- variant: variant,
- locals: locals
- )
+ build_template(template, path.virtual, locals)
end
end
+ def build_template(template, virtual_path, locals)
+ handler, format, variant = extract_handler_and_format_and_variant(template)
+
+ FileTemplate.new(File.expand_path(template), handler,
+ virtual_path: virtual_path,
+ format: format,
+ variant: variant,
+ locals: locals
+ )
+ end
+
def reject_files_external_to_app(files)
files.reject { |filename| !inside_path?(@path, filename) }
end
@@ -385,5 +389,9 @@ module ActionView
def self.instances
[new(""), new("/")]
end
+
+ def build_template(template, virtual_path, locals)
+ super(template, nil, locals)
+ end
end
end
diff --git a/actionview/test/template/fallback_file_system_resolver_test.rb b/actionview/test/template/fallback_file_system_resolver_test.rb
new file mode 100644
index 0000000000..304cdb8a03
--- /dev/null
+++ b/actionview/test/template/fallback_file_system_resolver_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class FallbackFileSystemResolverTest < ActiveSupport::TestCase
+ def setup
+ @root_resolver = ActionView::FallbackFileSystemResolver.new("/")
+ end
+
+ def test_should_have_no_virtual_path
+ templates = @root_resolver.find_all("hello_world.erb", "#{FIXTURE_LOAD_PATH}/test", false, locale: [], formats: [:html], variants: [], handlers: [:erb])
+ assert_equal 1, templates.size
+ assert_equal "Hello world!", templates[0].source
+ assert_nil templates[0].virtual_path
+ end
+end
diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb
index c5fc8f906c..17f21cbbc5 100644
--- a/actionview/test/template/html_test.rb
+++ b/actionview/test/template/html_test.rb
@@ -8,9 +8,9 @@ class HTMLTest < ActiveSupport::TestCase
end
test "formats returns string for recognized MIME type when MIME does not have symbol" do
- foo = Mime::Type.lookup("foo")
+ foo = Mime::Type.lookup("text/foo")
assert_nil foo.to_sym
- assert_equal "foo", ActionView::Template::HTML.new("", foo).format
+ assert_equal "text/foo", ActionView::Template::HTML.new("", foo).format
end
test "formats returns string for unknown MIME type" do
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 9d24d839c1..2877530917 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -138,6 +138,10 @@ module ActiveRecord
"'#{quote_string(value.to_s)}'"
end
+ def sanitize_as_sql_comment(value) # :nodoc:
+ value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
+ end
+
private
def type_casted_binds(binds)
if binds.first.is_a?(Array)
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index a6c702cbbc..4656045fe5 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -106,7 +106,7 @@ module ActiveRecord
build_db_config = configs.each_pair.flat_map do |env_name, config|
walk_configs(env_name.to_s, "primary", config)
- end.compact
+ end.flatten.compact
if url = ENV["DATABASE_URL"]
build_url_config(url, build_db_config)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 37179774fa..6d954a2b2e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -342,6 +342,8 @@ module ActiveRecord
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
# Active Record's normal type casting and serialization.
#
+ # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
+ #
# ==== Parameters
#
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
@@ -416,10 +418,10 @@ module ActiveRecord
update_all updates
end
- # Touches all records in the current relation without instantiating records first with the updated_at/on attributes
+ # Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
# set to the current time or the time specified.
# This method can be passed attribute names and an optional time argument.
- # If attribute names are passed, they are updated along with updated_at/on attributes.
+ # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
# If no time argument is passed, the current time is used as default.
#
# === Examples
diff --git a/activerecord/lib/arel/visitors/ibm_db.rb b/activerecord/lib/arel/visitors/ibm_db.rb
index 0ffc0725f7..5cf958f5f0 100644
--- a/activerecord/lib/arel/visitors/ibm_db.rb
+++ b/activerecord/lib/arel/visitors/ibm_db.rb
@@ -10,7 +10,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/* <OPTGUIDELINES>#{sanitize_as_sql_comment(o).join}</OPTGUIDELINES> */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join
+ collector << "/* <OPTGUIDELINES>#{hints}</OPTGUIDELINES> */"
end
def visit_Arel_Nodes_Limit(o, collector)
diff --git a/activerecord/lib/arel/visitors/informix.rb b/activerecord/lib/arel/visitors/informix.rb
index cd43be8858..1a4ad1c8d8 100644
--- a/activerecord/lib/arel/visitors/informix.rb
+++ b/activerecord/lib/arel/visitors/informix.rb
@@ -43,7 +43,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/*+ #{sanitize_as_sql_comment(o).join(", ")} */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
+ collector << "/*+ #{hints} */"
end
def visit_Arel_Nodes_Offset(o, collector)
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
index 85815baca2..8475139870 100644
--- a/activerecord/lib/arel/visitors/mssql.rb
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -82,7 +82,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "OPTION (#{sanitize_as_sql_comment(o).join(", ")})"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
+ collector << "OPTION (#{hints})"
end
def get_offset_limit_clause(o)
@@ -106,7 +107,7 @@ module Arel # :nodoc: all
collector = visit o.relation, collector
if o.wheres.any?
collector << " WHERE "
- inject_join o.wheres, collector, AND
+ inject_join o.wheres, collector, " AND "
else
collector
end
diff --git a/activerecord/lib/arel/visitors/postgresql.rb b/activerecord/lib/arel/visitors/postgresql.rb
index 920776b4dc..8296f1cdc1 100644
--- a/activerecord/lib/arel/visitors/postgresql.rb
+++ b/activerecord/lib/arel/visitors/postgresql.rb
@@ -3,11 +3,6 @@
module Arel # :nodoc: all
module Visitors
class PostgreSQL < Arel::Visitors::ToSql
- CUBE = "CUBE"
- ROLLUP = "ROLLUP"
- GROUPING_SETS = "GROUPING SETS"
- LATERAL = "LATERAL"
-
private
def visit_Arel_Nodes_Matches(o, collector)
@@ -57,23 +52,22 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_Cube(o, collector)
- collector << CUBE
+ collector << "CUBE"
grouping_array_or_grouping_element o, collector
end
def visit_Arel_Nodes_RollUp(o, collector)
- collector << ROLLUP
+ collector << "ROLLUP"
grouping_array_or_grouping_element o, collector
end
def visit_Arel_Nodes_GroupingSet(o, collector)
- collector << GROUPING_SETS
+ collector << "GROUPING SETS"
grouping_array_or_grouping_element o, collector
end
def visit_Arel_Nodes_Lateral(o, collector)
- collector << LATERAL
- collector << SPACE
+ collector << "LATERAL "
grouping_parentheses o, collector
end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 583f920290..1630226085 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -9,59 +9,6 @@ module Arel # :nodoc: all
end
class ToSql < Arel::Visitors::Visitor
- ##
- # This is some roflscale crazy stuff. I'm roflscaling this because
- # building SQL queries is a hotspot. I will explain the roflscale so that
- # others will not rm this code.
- #
- # In YARV, string literals in a method body will get duped when the byte
- # code is executed. Let's take a look:
- #
- # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
- #
- # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
- # 0000 trace 8
- # 0002 trace 1
- # 0004 putstring "bar"
- # 0006 trace 16
- # 0008 leave
- #
- # The `putstring` bytecode will dup the string and push it on the stack.
- # In many cases in our SQL visitor, that string is never mutated, so there
- # is no need to dup the literal.
- #
- # If we change to a constant lookup, the string will not be duped, and we
- # can reduce the objects in our system:
- #
- # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
- #
- # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
- # 0000 trace 8
- # 0002 trace 1
- # 0004 getinlinecache 11, <ic:0>
- # 0007 getconstant :BAR
- # 0009 setinlinecache <ic:0>
- # 0011 trace 16
- # 0013 leave
- #
- # `getconstant` should be a hash lookup, and no object is duped when the
- # value of the constant is pushed on the stack. Hence the crazy
- # constants below.
- #
- # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses
- # specialized for specific databases when necessary.
- #
-
- WHERE = " WHERE " # :nodoc:
- SPACE = " " # :nodoc:
- COMMA = ", " # :nodoc:
- GROUP_BY = " GROUP BY " # :nodoc:
- ORDER_BY = " ORDER BY " # :nodoc:
- WINDOW = " WINDOW " # :nodoc:
- AND = " AND " # :nodoc:
-
- DISTINCT = "DISTINCT" # :nodoc:
-
def initialize(connection)
super()
@connection = connection
@@ -161,10 +108,10 @@ module Arel # :nodoc: all
else
collector << quote(value).to_s
end
- collector << COMMA unless k == row_len
+ collector << ", " unless k == row_len
end
collector << ")"
- collector << COMMA unless i == len
+ collector << ", " unless i == len
}
collector
end
@@ -172,7 +119,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_SelectStatement(o, collector)
if o.with
collector = visit o.with, collector
- collector << SPACE
+ collector << " "
end
collector = o.cores.inject(collector) { |c, x|
@@ -180,11 +127,11 @@ module Arel # :nodoc: all
}
unless o.orders.empty?
- collector << ORDER_BY
+ collector << " ORDER BY "
len = o.orders.length - 1
o.orders.each_with_index { |x, i|
collector = visit(x, collector)
- collector << COMMA unless len == i
+ collector << ", " unless len == i
}
end
@@ -203,26 +150,27 @@ module Arel # :nodoc: all
collector = collect_optimizer_hints(o, collector)
collector = maybe_visit o.set_quantifier, collector
- collect_nodes_for o.projections, collector, SPACE
+ collect_nodes_for o.projections, collector, " "
if o.source && !o.source.empty?
collector << " FROM "
collector = visit o.source, collector
end
- collect_nodes_for o.wheres, collector, WHERE, AND
- collect_nodes_for o.groups, collector, GROUP_BY
- collect_nodes_for o.havings, collector, " HAVING ", AND
- collect_nodes_for o.windows, collector, WINDOW
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
+ collect_nodes_for o.groups, collector, " GROUP BY "
+ collect_nodes_for o.havings, collector, " HAVING ", " AND "
+ collect_nodes_for o.windows, collector, " WINDOW "
collector
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/*+ #{sanitize_as_sql_comment(o).join(" ")} */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
+ collector << "/*+ #{hints} */"
end
- def collect_nodes_for(nodes, collector, spacer, connector = COMMA)
+ def collect_nodes_for(nodes, collector, spacer, connector = ", ")
unless nodes.empty?
collector << spacer
inject_join nodes, collector, connector
@@ -234,7 +182,7 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_Distinct(o, collector)
- collector << DISTINCT
+ collector << "DISTINCT"
end
def visit_Arel_Nodes_DistinctOn(o, collector)
@@ -243,12 +191,12 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_With(o, collector)
collector << "WITH "
- inject_join o.children, collector, COMMA
+ inject_join o.children, collector, ", "
end
def visit_Arel_Nodes_WithRecursive(o, collector)
collector << "WITH RECURSIVE "
- inject_join o.children, collector, COMMA
+ inject_join o.children, collector, ", "
end
def visit_Arel_Nodes_Union(o, collector)
@@ -281,13 +229,13 @@ module Arel # :nodoc: all
collect_nodes_for o.partitions, collector, "PARTITION BY "
if o.orders.any?
- collector << SPACE if o.partitions.any?
+ collector << " " if o.partitions.any?
collector << "ORDER BY "
collector = inject_join o.orders, collector, ", "
end
if o.framing
- collector << SPACE if o.partitions.any? || o.orders.any?
+ collector << " " if o.partitions.any? || o.orders.any?
collector = visit o.framing, collector
end
@@ -492,8 +440,8 @@ module Arel # :nodoc: all
collector = visit o.left, collector
end
if o.right.any?
- collector << SPACE if o.left
- collector = inject_join o.right, collector, SPACE
+ collector << " " if o.left
+ collector = inject_join o.right, collector, " "
end
collector
end
@@ -513,7 +461,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_FullOuterJoin(o, collector)
collector << "FULL OUTER JOIN "
collector = visit o.left, collector
- collector << SPACE
+ collector << " "
visit o.right, collector
end
@@ -527,7 +475,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_RightOuterJoin(o, collector)
collector << "RIGHT OUTER JOIN "
collector = visit o.left, collector
- collector << SPACE
+ collector << " "
visit o.right, collector
end
@@ -535,7 +483,7 @@ module Arel # :nodoc: all
collector << "INNER JOIN "
collector = visit o.left, collector
if o.right
- collector << SPACE
+ collector << " "
visit(o.right, collector)
else
collector
@@ -785,10 +733,9 @@ module Arel # :nodoc: all
@connection.quote_column_name(name)
end
- def sanitize_as_sql_comment(o)
- o.expr.map { |v|
- v.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
- }
+ def sanitize_as_sql_comment(value)
+ return value if Arel::Nodes::SqlLiteral === value
+ @connection.sanitize_as_sql_comment(value)
end
def collect_optimizer_hints(o, collector)
diff --git a/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb b/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb
index 6830203cd6..5472e3c87b 100644
--- a/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb
+++ b/activestorage/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb
@@ -1,6 +1,8 @@
class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
def up
- unless foreign_key_exists?(:active_storage_attachments, column: :blob_id)
+ return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
+
+ if table_exists?(:active_storage_blobs)
add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b2330f2c9d..63e2e44597 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Fix `Time#advance` to work with dates before 1001-03-07
+
+ Before:
+
+ Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-05 00:00:00 UTC
+
+ After
+
+ Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-06 00:00:00 UTC
+
+ Note that this doesn't affect `DateTime#advance` as that doesn't use a proleptic calendar.
+
+ *Andrew White*
+
* In Zeitwerk mode, engines are now managed by the `main` autoloader. Engines may reference application constants, if the application is reloaded and we do not reload engines, they won't use the reloaded application code.
*Xavier Noria*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index e55d73c717..51f5086cca 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -34,5 +34,5 @@ Gem::Specification.new do |s|
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
- s.add_dependency "zeitwerk", "~> 1.3", ">= 1.3.4"
+ s.add_dependency "zeitwerk", "~> 1.4"
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 120768dec5..f09a6271ad 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -170,8 +170,7 @@ class Time
options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
end
- d = to_date.advance(options)
- d = d.gregorian if d.julian?
+ d = to_date.gregorian.advance(options)
time_advanced_by_date = change(year: d.year, month: d.month, day: d.day)
seconds_to_advance = \
options.fetch(:seconds, 0) +
diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
index e00307d257..c6fdade006 100644
--- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
+++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
@@ -21,7 +21,11 @@ module ActiveSupport
end
def autoloaded_constants
- (Rails.autoloaders.main.loaded + Rails.autoloaders.once.loaded).to_a
+ cpaths = []
+ Rails.autoloaders.each do |autoloader|
+ cpaths.concat(autoloader.loaded_cpaths.to_a)
+ end
+ cpaths
end
def autoloaded?(object)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 7078f3506d..590b81b770 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -514,6 +514,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(1582, 10, 15, 15, 15, 10), Time.local(1582, 10, 14, 15, 15, 10).advance(days: 1)
assert_equal Time.local(1582, 10, 5, 15, 15, 10), Time.local(1582, 10, 4, 15, 15, 10).advance(days: 1)
assert_equal Time.local(1582, 10, 4, 15, 15, 10), Time.local(1582, 10, 5, 15, 15, 10).advance(days: -1)
+ assert_equal Time.local(999, 10, 4, 15, 15, 10), Time.local(1000, 10, 4, 15, 15, 10).advance(years: -1)
+ assert_equal Time.local(1000, 10, 4, 15, 15, 10), Time.local(999, 10, 4, 15, 15, 10).advance(years: 1)
end
def test_last_week
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 14cdf1ab7c..3fcfaa9623 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -123,6 +123,8 @@ module TestHelpers
adapter: sqlite3
pool: 5
timeout: 5000
+ variables:
+ statement_timeout: 1000
development:
primary:
<<: *default