aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock4
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb5
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb105
-rw-r--r--actiontext/app/helpers/action_text/tag_helper.rb4
-rw-r--r--actiontext/test/template/form_helper_test.rb50
-rw-r--r--actiontext/test/test_helper.rb3
-rw-r--r--actionview/lib/action_view/layouts.rb10
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb13
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb9
-rw-r--r--actionview/lib/action_view/rendering.rb3
-rw-r--r--actionview/lib/action_view/template.rb1
-rw-r--r--actionview/lib/action_view/template/inline.rb22
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb17
-rw-r--r--actionview/test/template/form_helper_test.rb72
-rw-r--r--activejob/lib/active_job/core.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/timestamp.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb9
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb40
-rw-r--r--activerecord/test/cases/finder_test.rb6
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb38
-rw-r--r--activerecord/test/cases/relation/where_test.rb22
-rw-r--r--activerecord/test/cases/relations_test.rb61
-rw-r--r--activerecord/test/support/stubs/strong_parameters.rb21
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/dependencies/zeitwerk_integration.rb18
-rw-r--r--guides/source/active_record_callbacks.md5
-rw-r--r--guides/source/active_record_querying.md2
-rw-r--r--guides/source/getting_started.md4
-rw-r--r--railties/lib/rails/autoloaders.rb3
-rw-r--r--railties/test/application/zeitwerk_integration_test.rb37
36 files changed, 386 insertions, 234 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index ce50c043e2..a291e94936 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,7 +70,7 @@ PATH
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- zeitwerk (~> 1.2)
+ zeitwerk (~> 1.3, >= 1.3.1)
rails (6.0.0.beta1)
actioncable (= 6.0.0.beta1)
actionmailbox (= 6.0.0.beta1)
@@ -517,7 +517,7 @@ GEM
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (1.2.0)
+ zeitwerk (1.3.1)
PLATFORMS
java
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 827f022ca2..0da8f5c14e 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -20,7 +20,6 @@ module ActionDispatch
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
attr_accessor :tempfile
- alias :to_io :tempfile
# A string with the headers of the multipart request.
attr_accessor :headers
@@ -84,6 +83,10 @@ module ActionDispatch
def eof?
@tempfile.eof?
end
+
+ def to_io
+ @tempfile.to_io
+ end
end
end
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 21169fcb5c..03e5274541 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "abstract_unit"
+require "tempfile"
+require "stringio"
module ActionDispatch
class UploadedFileTest < ActiveSupport::TestCase
@@ -11,109 +13,118 @@ module ActionDispatch
end
def test_original_filename
- uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.original_filename
end
def test_filename_is_different_object
file_str = "foo"
- uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: file_str, tempfile: Tempfile.new)
assert_not_equal file_str.object_id, uf.original_filename.object_id
end
def test_filename_should_be_in_utf_8
- uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
def test_filename_should_always_be_in_utf_8
uf = Http::UploadedFile.new(filename: "foo".encode(Encoding::SHIFT_JIS),
- tempfile: Object.new)
+ tempfile: Tempfile.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
def test_content_type
- uf = Http::UploadedFile.new(type: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(type: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.content_type
end
def test_headers
- uf = Http::UploadedFile.new(head: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(head: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.headers
end
def test_tempfile
- uf = Http::UploadedFile.new(tempfile: "foo")
- assert_equal "foo", uf.tempfile
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf, uf.tempfile
end
- def test_to_io_returns_the_tempfile
- tf = Object.new
+ def test_to_io_returns_file
+ tf = Tempfile.new
uf = Http::UploadedFile.new(tempfile: tf)
- assert_equal tf, uf.to_io
+ assert_equal tf.to_io, uf.to_io
end
def test_delegates_path_to_tempfile
- tf = Class.new { def path; "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.path
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf.path, uf.path
end
def test_delegates_open_to_tempfile
- tf = Class.new { def open; "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.open
+ tf = Tempfile.new
+ tf.close
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf, uf.open
+ assert_not tf.closed?
end
def test_delegates_close_to_tempfile
- tf = Class.new { def close(unlink_now = false); "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.close
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ uf.close
+ assert tf.closed?
end
def test_close_accepts_parameter
- tf = Class.new { def close(unlink_now = false); "thunderhorse: #{unlink_now}" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse: true", uf.close(true)
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ uf.close(true)
+ assert tf.closed?
+ assert_nil tf.path
end
def test_delegates_read_to_tempfile
- tf = Class.new { def read(length = nil, buffer = nil); "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
assert_equal "thunderhorse", uf.read
end
def test_delegates_read_to_tempfile_with_params
- tf = Class.new { def read(length = nil, buffer = nil); [length, buffer] end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse })
- end
-
- def test_delegate_respects_respond_to?
- tf = Class.new { def read; yield end; private :read }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_raises(NoMethodError) do
- uf.read
- end
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal "thunder", uf.read(7)
+ assert_equal "horse", uf.read(5, String.new)
end
def test_delegate_eof_to_tempfile
- tf = Class.new { def eof?; true end; }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_predicate uf, :eof?
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal true, uf.eof?
+ tf.rewind
+ assert_equal false, uf.eof?
end
def test_delegate_to_path_to_tempfile
- tf = Class.new { def to_path; "/any/file/path" end; }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "/any/file/path", uf.to_path
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf.to_path, uf.to_path
end
- def test_respond_to?
- tf = Class.new { def read; yield end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_respond_to uf, :headers
- assert_respond_to uf, :read
+ def test_io_copy_stream
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
+ result = StringIO.new
+ IO.copy_stream(uf, result)
+ assert_equal "thunderhorse", result.string
end
end
end
diff --git a/actiontext/app/helpers/action_text/tag_helper.rb b/actiontext/app/helpers/action_text/tag_helper.rb
index 8434f2c611..1dc6202ae1 100644
--- a/actiontext/app/helpers/action_text/tag_helper.rb
+++ b/actiontext/app/helpers/action_text/tag_helper.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "action_view/helpers/tags/placeholderable"
+
module ActionText
module TagHelper
cattr_accessor(:id, instance_accessor: false) { 0 }
@@ -35,6 +37,8 @@ end
module ActionView::Helpers
class Tags::ActionText < Tags::Base
+ include Tags::Placeholderable
+
delegate :dom_id, to: ActionView::RecordIdentifier
def render
diff --git a/actiontext/test/template/form_helper_test.rb b/actiontext/test/template/form_helper_test.rb
index a8c7a4dae2..cf7e4c0c69 100644
--- a/actiontext/test/template/form_helper_test.rb
+++ b/actiontext/test/template/form_helper_test.rb
@@ -5,6 +5,26 @@ require "test_helper"
class ActionText::FormHelperTest < ActionView::TestCase
tests ActionText::TagHelper
+ def form_with(*)
+ @output_buffer = super
+ end
+
+ teardown do
+ I18n.backend.reload!
+ end
+
+ setup do
+ I18n.backend.store_translations("placeholder",
+ activerecord: {
+ attributes: {
+ message: {
+ title: "Story title"
+ }
+ }
+ }
+ )
+ end
+
test "form with rich text area" do
form_with model: Message.new, scope: :message do |form|
form.rich_text_area :content
@@ -61,7 +81,33 @@ class ActionText::FormHelperTest < ActionView::TestCase
output_buffer
end
- def form_with(*)
- @output_buffer = super
+ test "form with rich text area having placeholder without locale" do
+ form_with model: Message.new, scope: :message do |form|
+ form.rich_text_area :content, placeholder: true
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[content]" id="message_content_trix_input_message" />' \
+ '<trix-editor placeholder="Content" id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
+ end
+
+ test "form with rich text area having placeholder with locale" do
+ I18n.with_locale :placeholder do
+ form_with model: Message.new, scope: :message do |form|
+ form.rich_text_area :title, placeholder: true
+ end
+ end
+
+ assert_dom_equal \
+ '<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post">' \
+ '<input type="hidden" name="message[title]" id="message_title_trix_input_message" />' \
+ '<trix-editor placeholder="Story title" id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/:signed_id/:filename">' \
+ "</trix-editor>" \
+ "</form>",
+ output_buffer
end
end
diff --git a/actiontext/test/test_helper.rb b/actiontext/test/test_helper.rb
index fd1c859349..196fba8c99 100644
--- a/actiontext/test/test_helper.rb
+++ b/actiontext/test/test_helper.rb
@@ -14,6 +14,9 @@ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
require "rails/test_unit/reporter"
Rails::TestUnitReporter.executable = "bin/test"
+# Disable available locale checks to allow to add locale after initialized.
+I18n.enforce_available_locales = false
+
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 3e6d352c15..08f66bf435 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -322,7 +322,7 @@ module ActionView
end
class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout(formats)
+ def _layout(lookup_context, formats)
if _conditional_layout?
#{layout_definition}
else
@@ -388,8 +388,8 @@ module ActionView
case name
when String then _normalize_layout(name)
when Proc then name
- when true then Proc.new { |formats| _default_layout(formats, true) }
- when :default then Proc.new { |formats| _default_layout(formats, false) }
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
when false, nil then nil
else
raise ArgumentError,
@@ -411,9 +411,9 @@ module ActionView
#
# ==== Returns
# * <tt>template</tt> - The template object for the default layout (or +nil+)
- def _default_layout(formats, require_layout = false)
+ def _default_layout(lookup_context, formats, require_layout = false)
begin
- value = _layout(formats) if action_has_layout?
+ value = _layout(lookup_context, formats) if action_has_layout?
rescue NameError => e
raise e, "Could not render layout: #{e.message}"
end
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 200dc3e10e..f1b4c9b92d 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -28,6 +28,10 @@ module ActionView
end
class RenderedCollection # :nodoc:
+ def self.empty(format)
+ EmptyCollection.new format
+ end
+
attr_reader :rendered_templates
def initialize(rendered_templates, spacer)
@@ -44,11 +48,14 @@ module ActionView
end
class EmptyCollection
- def format; nil; end
+ attr_reader :format
+
+ def initialize(format)
+ @format = format
+ end
+
def body; nil; end
end
-
- EMPTY = EmptyCollection.new
end
class RenderedTemplate # :nodoc:
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 4ae6f635ae..ed8d5cf54e 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -326,7 +326,7 @@ module ActionView
def render_collection(view, template)
identifier = (template && template.identifier) || @path
instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
- return RenderedCollection::EMPTY if @collection.blank?
+ return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
spacer = if @options.key?(:spacer_template)
spacer_template = find_template(@options[:spacer_template], @locals.keys)
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index c17d6182e8..87f6cf3de3 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -29,7 +29,12 @@ module ActionView
@lookup_context.with_fallbacks.find_file(options[:file], nil, false, keys, @details)
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
- Template.new(options[:inline], "inline template", handler, locals: keys)
+ format = if handler.respond_to?(:default_format)
+ handler.default_format
+ else
+ @lookup_context.formats.first
+ end
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
elsif options.key?(:template)
if options[:template].respond_to?(:render)
options[:template]
@@ -88,7 +93,7 @@ module ActionView
raise unless template_exists?(layout, nil, false, [], all_details)
end
when Proc
- resolve_layout(layout.call(formats), keys, formats)
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
else
layout
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index ac861c44d4..e5e2771323 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -118,8 +118,7 @@ module ActionView
renderer.render_to_object(context, options)
end
- rendered_format = rendered_template.format || lookup_context.formats.first
- @rendered_format = Template::Types[rendered_format]
+ @rendered_format = Template::Types[rendered_template.format]
rendered_template.body
end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 907b322c8a..133a316405 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -115,6 +115,7 @@ module ActionView
autoload :Error
autoload :Handlers
autoload :HTML
+ autoload :Inline
autoload :Text
autoload :Types
end
diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb
new file mode 100644
index 0000000000..44658487ea
--- /dev/null
+++ b/actionview/lib/action_view/template/inline.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActionView #:nodoc:
+ class Template #:nodoc:
+ class Inline < Template #:nodoc:
+ # This finalizer is needed (and exactly with a proc inside another proc)
+ # otherwise templates leak in development.
+ Finalizer = proc do |method_name, mod| # :nodoc:
+ proc do
+ mod.module_eval do
+ remove_possible_method method_name
+ end
+ end
+ end
+
+ def compile(mod)
+ super
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
+ end
+ end
+ end
+end
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index f84c9b2b73..42069340f1 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -994,7 +994,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_as_new_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
@post.persisted = false
@post.stub(:to_key, nil) do
form_with(model: @post) do |f|
@@ -1011,7 +1011,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_as_existing_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: @post) do |f|
concat f.submit
end
@@ -1025,7 +1025,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_without_object_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(scope: :post) do |f|
concat f.submit class: "extra"
end
@@ -1039,7 +1039,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_which_is_overwritten_by_scope_option
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: @post, scope: :another_post) do |f|
concat f.submit
end
@@ -1054,7 +1054,7 @@ class FormWithActsLikeFormForTest < FormWithTest
def test_submit_with_object_which_is_namespaced
blog_post = Blog::Post.new("And his name will be forty and four.", 44)
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: blog_post) do |f|
concat f.submit
end
@@ -2357,11 +2357,4 @@ class FormWithActsLikeFormForTest < FormWithTest
def protect_against_forgery?
false
end
-
- def with_locale(testing_locale = :label)
- old_locale, I18n.locale = I18n.locale, testing_locale
- yield
- ensure
- I18n.locale = old_locale
- end
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 5972946074..91052e5ae2 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -203,31 +203,31 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_strings
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body"))
end
end
def test_label_with_human_attribute_name
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost))
end
end
def test_label_with_human_attribute_name_and_options
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_language_spanish">Espanol</label>', label(:post, :language, value: "spanish"))
end
end
def test_label_with_locales_symbols
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
end
end
def test_label_with_locales_and_options
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
'<label for="post_body" class="post_body">Write entire text here</label>',
label(:post, :body, class: "post_body")
@@ -236,13 +236,13 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_and_value
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red"))
end
end
def test_label_with_locales_and_nested_attributes
- with_locale :label do
+ I18n.with_locale :label do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.label(:body)
@@ -258,7 +258,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_fallback_and_nested_attributes
- with_locale :label do
+ I18n.with_locale :label do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.label(:value)
@@ -358,7 +358,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_block_and_builder
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
'<label for="post_body"><b>Write entire text here</b></label>',
label(:post, :body) { |b| raw("<b>#{b.translation}</b>") }
@@ -381,7 +381,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_to_model_and_overridden_model_name
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
%{<label for="post_delegator_title">Delegate model_name title</label>},
label(:post_delegator, :title)
@@ -390,19 +390,19 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_without_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_body" name="post[body]" placeholder="Body" type="text" value="Back to the hill and over it again!" />', text_field(:post, :body, placeholder: true))
end
end
def test_text_field_placeholder_with_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_title" name="post[title]" placeholder="What is this about?" type="text" value="Hello World" />', text_field(:post, :title, placeholder: true))
end
end
def test_text_field_placeholder_with_locales_and_to_model
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
'<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate model_name title" type="text" value="Hello World" />',
text_field(:post_delegator, :title, placeholder: true)
@@ -411,7 +411,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_human_attribute_name
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Total cost" type="text" />', text_field(:post, :cost, placeholder: true))
end
end
@@ -424,25 +424,25 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_string_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?"))
end
end
def test_text_field_placeholder_with_human_attribute_name_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Pounds" type="text" />', text_field(:post, :cost, placeholder: :uk))
end
end
def test_text_field_placeholder_with_locales_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_written_on" name="post[written_on]" placeholder="Escrito en" type="text" value="2004-06-15" />', text_field(:post, :written_on, placeholder: :spanish))
end
end
def test_text_field_placeholder_with_locales_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.text_field(:body, placeholder: true)
@@ -458,7 +458,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_locales_fallback_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.text_field(:value, placeholder: true)
@@ -861,7 +861,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_without_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_body" name="post[body]" placeholder="Body">\nBack to the hill and over it again!</textarea>},
text_area(:post, :body, placeholder: true)
@@ -870,7 +870,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_title" name="post[title]" placeholder="What is this about?">\nHello World</textarea>},
text_area(:post, :title, placeholder: true)
@@ -879,7 +879,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_human_attribute_name
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="Total cost">\n</textarea>},
text_area(:post, :cost, placeholder: true)
@@ -888,7 +888,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_string_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="HOW MUCH?">\n</textarea>},
text_area(:post, :cost, placeholder: "HOW MUCH?")
@@ -897,7 +897,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_human_attribute_name_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="Pounds">\n</textarea>},
text_area(:post, :cost, placeholder: :uk)
@@ -906,7 +906,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_written_on" name="post[written_on]" placeholder="Escrito en">\n2004-06-15</textarea>},
text_area(:post, :written_on, placeholder: :spanish)
@@ -915,7 +915,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.text_area(:body, placeholder: true)
@@ -931,7 +931,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_fallback_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.text_area(:value, placeholder: true)
@@ -2260,7 +2260,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_as_new_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
@post.persisted = false
@post.stub(:to_key, nil) do
form_for(@post) do |f|
@@ -2277,7 +2277,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_as_existing_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(@post) do |f|
concat f.submit
end
@@ -2291,7 +2291,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_without_object_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(:post) do |f|
concat f.submit class: "extra"
end
@@ -2305,7 +2305,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_which_is_overwritten_by_as_option
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(@post, as: :another_post) do |f|
concat f.submit
end
@@ -2320,7 +2320,7 @@ class FormHelperTest < ActionView::TestCase
def test_submit_with_object_which_is_namespaced
blog_post = Blog::Post.new("And his name will be forty and four.", 44)
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(blog_post) do |f|
concat f.submit
end
@@ -3554,7 +3554,6 @@ class FormHelperTest < ActionView::TestCase
end
private
-
def hidden_fields(options = {})
method = options[:method]
@@ -3593,13 +3592,6 @@ class FormHelperTest < ActionView::TestCase
false
end
- def with_locale(testing_locale = :label)
- old_locale, I18n.locale = I18n.locale, testing_locale
- yield
- ensure
- I18n.locale = old_locale
- end
-
def with_default_enforce_utf8(value)
old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 2ce008e3da..283125698d 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -40,7 +40,7 @@ module ActiveJob
# Timezone to be used during the job.
attr_accessor :timezone
- # Track when a job was enqueded
+ # Track when a job was enqueued
attr_accessor :enqueued_at
# These methods will be included into any Active Job object, adding
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 6b927e9797..ef5444dfc3 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -324,7 +324,7 @@ module ActiveRecord
private
- def create_or_update(*)
+ def create_or_update(**)
_run_save_callbacks { super }
end
@@ -332,7 +332,7 @@ module ActiveRecord
_run_create_callbacks { super }
end
- def _update_record(*)
+ def _update_record
_run_update_callbacks { super }
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index b202a82f55..b2cc60f363 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -852,7 +852,7 @@ module ActiveRecord
# [<tt>:null</tt>]
# Whether the column allows nulls. Defaults to true.
#
- # ====== Create a user_id bigint column without a index
+ # ====== Create a user_id bigint column without an index
#
# add_reference(:products, :user, index: false)
#
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7763496519..10148d0dca 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -707,10 +707,10 @@ module ActiveRecord
)
end
- def create_or_update(*args, &block)
+ def create_or_update(**, &block)
_raise_readonly_record_error if readonly?
return false if destroyed?
- result = new_record? ? _create_record(&block) : _update_record(*args, &block)
+ result = new_record? ? _create_record(&block) : _update_record(&block)
result != false
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 5563dfb6c9..f69b85af66 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1068,8 +1068,10 @@ module ActiveRecord
def arel_column(field)
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
+ from = from_clause.name || from_clause.value
- if klass.columns_hash.key?(field) && !from_clause.value
+ if klass.columns_hash.key?(field) &&
+ (!from || from == table.name || from == connection.quote_table_name(table.name))
arel_attribute(field)
else
yield
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 2345db7138..04a1c03474 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -101,8 +101,8 @@ module ActiveRecord
super
end
- def _update_record(*args, touch: true, **options)
- if touch && should_record_timestamps?
+ def _update_record
+ if @_touch_record && should_record_timestamps?
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |column|
@@ -110,7 +110,13 @@ module ActiveRecord
_write_attribute(column, current_time)
end
end
- super(*args)
+
+ super
+ end
+
+ def create_or_update(touch: true, **)
+ @_touch_record = touch
+ super
end
def should_record_timestamps?
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 4b061a9375..cd45975f70 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -2,6 +2,7 @@
require "cases/helper"
require "support/schema_dumping_helper"
+require "support/stubs/strong_parameters"
class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
@@ -11,12 +12,6 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
store_accessor :settings, :language, :timezone
end
- class FakeParameters
- def to_unsafe_h
- { "hi" => "hi" }
- end
- end
-
def setup
@connection = ActiveRecord::Base.connection
@@ -344,7 +339,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
end
def test_supports_to_unsafe_h_values
- assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ assert_equal "\"hi\"=>\"hi\"", @type.serialize(ProtectedParams.new("hi" => "hi"))
end
private
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 148c9dd347..12ff6d4826 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -433,6 +433,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
assert_equal true, Topic.new(author_name: "Name").author_name?
+
+ ActiveModel::Type::Boolean::FALSE_VALUES.each do |value|
+ assert_predicate Topic.new(author_name: value), :author_name?
+ end
end
test "number attribute predicate" do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index b001667ac9..8ac2d55218 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -19,6 +19,7 @@ require "models/developer"
require "models/post"
require "models/comment"
require "models/rating"
+require "support/stubs/strong_parameters"
class CalculationsTest < ActiveRecord::TestCase
fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
@@ -278,6 +279,18 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).offset(2).count
end
+ def test_distinct_joins_count_with_group_by
+ expected = { nil => 4, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:author_id)
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).count("DISTINCT posts.author_id")
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count
+
+ expected = { nil => 6, 1 => 1, 2 => 1, 4 => 1, 5 => 1, 7 => 1 }
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.count(:all)
+ assert_equal expected, Post.left_joins(:comments).group(:post_id).distinct.select(:author_id).count(:all)
+ end
+
def test_distinct_count_with_group_by_and_order_and_limit
assert_equal({ 6 => 2 }, Account.group(:firm_id).distinct.order("1 DESC").limit(1).count)
end
@@ -511,8 +524,10 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_field_of_root_table_with_conflicting_group_by_column
- assert_equal({ 1 => 1 }, Firm.joins(:accounts).group(:firm_id).count)
- assert_equal({ 1 => 1 }, Firm.joins(:accounts).group("accounts.firm_id").count)
+ expected = { 1 => 2, 2 => 1, 4 => 5, 5 => 2, 7 => 1 }
+ assert_equal expected, Post.joins(:comments).group(:post_id).count
+ assert_equal expected, Post.joins(:comments).group("comments.post_id").count
+ assert_equal expected, Post.joins(:comments).group(:post_id).select("DISTINCT posts.author_id").count(:all)
end
def test_count_with_no_parameters_isnt_deprecated
@@ -883,26 +898,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_having_with_strong_parameters
- protected_params = Class.new do
- attr_reader :permitted
- alias :permitted? :permitted
-
- def initialize(parameters)
- @parameters = parameters
- @permitted = false
- end
-
- def to_h
- @parameters
- end
-
- def permit!
- @permitted = true
- self
- end
- end
-
- params = protected_params.new(credit_limit: "50")
+ params = ProtectedParams.new(credit_limit: "50")
assert_raises(ActiveModel::ForbiddenAttributesError) do
Account.group(:id).having(params)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 4040682280..f9792bf8d3 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -226,14 +226,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_with_strong_parameters
- assert_equal false, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+ assert_equal false, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
Subscriber.create!(nick: "foo")
- assert_equal true, Subscriber.exists?(Parameters.new(nick: "foo").permit!)
+ assert_equal true, Subscriber.exists?(ProtectedParams.new(nick: "foo").permit!)
assert_raises(ActiveModel::ForbiddenAttributesError) do
- Subscriber.exists?(Parameters.new(nick: "foo"))
+ Subscriber.exists?(ProtectedParams.new(nick: "foo"))
end
end
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index 101fa118c8..e7e31b6d2d 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -1,48 +1,12 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_support/core_ext/hash/indifferent_access"
-
require "models/company"
require "models/person"
require "models/ship"
require "models/ship_part"
require "models/treasure"
-
-class ProtectedParams
- attr_accessor :permitted
- alias :permitted? :permitted
-
- delegate :keys, :key?, :has_key?, :empty?, to: :@parameters
-
- def initialize(attributes)
- @parameters = attributes.with_indifferent_access
- @permitted = false
- end
-
- def permit!
- @permitted = true
- self
- end
-
- def [](key)
- @parameters[key]
- end
-
- def to_h
- @parameters
- end
-
- def stringify_keys
- dup
- end
-
- def dup
- super.tap do |duplicate|
- duplicate.instance_variable_set :@permitted, @permitted
- end
- end
-end
+require "support/stubs/strong_parameters"
class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
def test_forbidden_attributes_cannot_be_used_for_mass_assignment
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 5c729e68cd..b045184d7d 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -14,6 +14,7 @@ require "models/price_estimate"
require "models/topic"
require "models/treasure"
require "models/vertex"
+require "support/stubs/strong_parameters"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
@@ -339,27 +340,8 @@ module ActiveRecord
end
def test_where_with_strong_parameters
- protected_params = Class.new do
- attr_reader :permitted
- alias :permitted? :permitted
-
- def initialize(parameters)
- @parameters = parameters
- @permitted = false
- end
-
- def to_h
- @parameters
- end
-
- def permit!
- @permitted = true
- self
- end
- end
-
author = authors(:david)
- params = protected_params.new(name: author.name)
+ params = ProtectedParams.new(name: author.name)
assert_raises(ActiveModel::ForbiddenAttributesError) { Author.where(params) }
assert_equal author, Author.where(params.permit!).first
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index adc50a694e..2de0a81c99 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -182,6 +182,43 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_select_with_original_table_name_in_from
+ relation = Comment.joins(:post).select(:id).order(:id)
+ subquery = Comment.from(Comment.table_name).joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_original_table_name_in_from
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from(Comment.table_name).joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
+ def test_select_with_quoted_original_table_name_in_from
+ relation = Comment.joins(:post).select(:id).order(:id)
+ subquery = Comment.from(Comment.quoted_table_name).joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_quoted_original_table_name_in_from
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from(Comment.quoted_table_name).joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
+ def test_select_with_subquery_in_from_uses_original_table_name
+ relation = Comment.joins(:post).select(:id).order(:id)
+ # Avoid subquery flattening by adding distinct to work with SQLite < 3.20.0.
+ subquery = Comment.from(Comment.all.distinct, Comment.quoted_table_name).joins(:post).select(:id).order(:id)
+ assert_equal relation.map(&:id), subquery.map(&:id)
+ end
+
+ def test_pluck_with_subquery_in_from_uses_original_table_name
+ relation = Comment.joins(:post).order(:id)
+ subquery = Comment.from(Comment.all, Comment.quoted_table_name).joins(:post).order(:id)
+ assert_equal relation.pluck(:id), subquery.pluck(:id)
+ end
+
def test_select_with_subquery_in_from_does_not_use_original_table_name
relation = Comment.group(:type).select("COUNT(post_id) AS post_count, type")
subquery = Comment.from(relation).select("type", "post_count")
@@ -1904,6 +1941,30 @@ class RelationTest < ActiveRecord::TestCase
assert_equal p2.first.comments, comments
end
+ def test_unscope_with_merge
+ p0 = Post.where(author_id: 0)
+ p1 = Post.where(author_id: 1, comments_count: 1)
+
+ assert_equal [posts(:authorless)], p0
+ assert_equal [posts(:thinking)], p1
+
+ comments = Comment.merge(p0).unscope(where: :author_id).where(post: p1)
+
+ assert_not_equal p0.first.comments, comments
+ assert_equal p1.first.comments, comments
+ end
+
+ def test_unscope_with_unknown_column
+ comment = comments(:greetings)
+ comment.update!(comments: 1)
+
+ comments = Comment.where(comments: 1).unscope(where: :unknown_column)
+ assert_equal [comment], comments
+
+ comments = Comment.where(comments: 1).unscope(where: { comments: :unknown_column })
+ assert_equal [comment], comments
+ end
+
def test_unscope_specific_where_value
posts = Post.where(title: "Welcome to the weblog", body: "Such a lovely day")
diff --git a/activerecord/test/support/stubs/strong_parameters.rb b/activerecord/test/support/stubs/strong_parameters.rb
index 84f93a28b9..da8f9892f9 100644
--- a/activerecord/test/support/stubs/strong_parameters.rb
+++ b/activerecord/test/support/stubs/strong_parameters.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-class Parameters
+require "active_support/core_ext/hash/indifferent_access"
+
+class ProtectedParams
+ delegate :keys, :key?, :has_key?, :empty?, to: :@parameters
+
def initialize(parameters = {})
@parameters = parameters.with_indifferent_access
@permitted = false
@@ -15,7 +19,22 @@ class Parameters
self
end
+ def [](key)
+ @parameters[key]
+ end
+
def to_h
@parameters.to_h
end
+ alias to_unsafe_h to_h
+
+ def stringify_keys
+ dup
+ end
+
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, @permitted
+ end
+ end
end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 2fa0623a9c..c92691afaf 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.2"
+ s.add_dependency "zeitwerk", "~> 1.3", ">= 1.3.1"
end
diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
index 939ada123d..ca4385b7c2 100644
--- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
+++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/core_ext/string/inflections"
+
module ActiveSupport
module Dependencies
module ZeitwerkIntegration # :nodoc: all
@@ -11,11 +13,11 @@ module ActiveSupport
end
def constantize(cpath)
- Inflector.constantize(cpath)
+ ActiveSupport::Inflector.constantize(cpath)
end
def safe_constantize(cpath)
- Inflector.safe_constantize(cpath)
+ ActiveSupport::Inflector.safe_constantize(cpath)
end
def autoloaded_constants
@@ -28,7 +30,7 @@ module ActiveSupport
end
def verbose=(verbose)
- l = verbose ? (logger || Rails.logger).method(:debug) : nil
+ l = verbose ? logger || Rails.logger : nil
Rails.autoloaders.each { |autoloader| autoloader.logger = l }
end
@@ -37,6 +39,12 @@ module ActiveSupport
end
end
+ module Inflector
+ def self.camelize(basename, _abspath)
+ basename.camelize
+ end
+ end
+
class << self
def take_over
setup_autoloaders
@@ -47,6 +55,10 @@ module ActiveSupport
private
def setup_autoloaders
+ Rails.autoloaders.each do |autoloader|
+ autoloader.inflector = Inflector
+ end
+
Dependencies.autoload_paths.each do |autoload_path|
# Zeitwerk only accepts existing directories in `push_dir` to
# prevent misconfigurations.
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 4568b467ef..614737c342 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -239,13 +239,12 @@ Skipping Callbacks
Just as with validations, it is also possible to skip callbacks by using the following methods:
-* `decrement`
+* `decrement!`
* `decrement_counter`
* `delete`
* `delete_all`
-* `increment`
+* `increment!`
* `increment_counter`
-* `toggle`
* `update_column`
* `update_columns`
* `update_all`
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index fd1dcf22c0..cb738f0657 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -623,6 +623,8 @@ To select only a subset of fields from the result set, you can specify the subse
For example, to select only `viewable_by` and `locked` columns:
```ruby
+Client.select(:viewable_by, :locked)
+# OR
Client.select("viewable_by, locked")
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index be59cd0cfa..7ee0d8c916 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -683,7 +683,7 @@ If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
(remember, yours will have a slightly different name), here's what you'll find:
```ruby
-class CreateArticles < ActiveRecord::Migration[5.0]
+class CreateArticles < ActiveRecord::Migration[6.0]
def change
create_table :articles do |t|
t.string :title
@@ -1555,7 +1555,7 @@ In addition to the model, Rails has also made a migration to create the
corresponding database table:
```ruby
-class CreateComments < ActiveRecord::Migration[5.0]
+class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :commenter
diff --git a/railties/lib/rails/autoloaders.rb b/railties/lib/rails/autoloaders.rb
index 4b41e1faf9..a6974cc207 100644
--- a/railties/lib/rails/autoloaders.rb
+++ b/railties/lib/rails/autoloaders.rb
@@ -25,8 +25,7 @@ module Rails
end
def logger=(logger)
- callable_or_nil = logger.respond_to?(:debug) ? logger.method(:debug) : logger
- each { |loader| loader.logger = callable_or_nil }
+ each { |loader| loader.logger = logger }
end
def zeitwerk_enabled?
diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb
index 8a8ca18ebf..cddbf5a22d 100644
--- a/railties/test/application/zeitwerk_integration_test.rb
+++ b/railties/test/application/zeitwerk_integration_test.rb
@@ -47,6 +47,31 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
assert_equal 0, Rails.autoloaders.count
end
+ test "autoloaders inflect with Active Support" do
+ app_file "config/initializers/inflections.rb", <<-RUBY
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
+ inflect.acronym 'RESTful'
+ end
+ RUBY
+
+ app_file "app/controllers/restful_controller.rb", <<-RUBY
+ class RESTfulController < ApplicationController
+ end
+ RUBY
+
+ boot
+
+ basename = "restful_controller"
+ abspath = "#{Rails.root}/app/controllers/#{basename}.rb"
+ camelized = "RESTfulController"
+
+ Rails.autoloaders.each do |autoloader|
+ assert_equal camelized, autoloader.inflector.camelize(basename, abspath)
+ end
+
+ assert RESTfulController
+ end
+
test "constantize returns the value stored in the constant" do
app_file "app/models/admin/user.rb", "class Admin::User; end"
boot
@@ -164,7 +189,7 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
assert_equal %i(main_autoloader), $zeitwerk_integration_reload_test
end
- test "verbose = true sets the debug method of the dependencies logger if present" do
+ test "verbose = true sets the dependencies logger if present" do
boot
logger = Logger.new(File::NULL)
@@ -172,17 +197,17 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.verbose = true
Rails.autoloaders.each do |autoloader|
- assert_equal logger.method(:debug), autoloader.logger
+ assert_same logger, autoloader.logger
end
end
- test "verbose = true sets the debug method of the Rails logger as fallback" do
+ test "verbose = true sets the Rails logger as fallback" do
boot
ActiveSupport::Dependencies.verbose = true
Rails.autoloaders.each do |autoloader|
- assert_equal Rails.logger.method(:debug), autoloader.logger
+ assert_same Rails.logger, autoloader.logger
end
end
@@ -214,13 +239,13 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
Rails.autoloaders.logger = logger
Rails.autoloaders.each do |autoloader|
- assert_equal logger, autoloader.logger
+ assert_same logger, autoloader.logger
end
Rails.autoloaders.logger = Rails.logger
Rails.autoloaders.each do |autoloader|
- assert_equal Rails.logger.method(:debug), autoloader.logger
+ assert_same Rails.logger, autoloader.logger
end
Rails.autoloaders.logger = nil