aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/stale.yml6
-rw-r--r--.travis.yml23
-rw-r--r--Gemfile.lock2
-rw-r--r--actioncable/test/client_test.rb31
-rw-r--r--actionmailer/test/parameterized_test.rb1
-rw-r--r--actionpack/CHANGELOG.md12
-rw-r--r--actionpack/lib/action_controller/api.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb6
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb11
-rw-r--r--actionpack/test/controller/api/with_helpers_test.rb16
-rw-r--r--actionpack/test/controller/base_test.rb27
-rw-r--r--actionpack/test/controller/metal_test.rb30
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb16
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb18
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb11
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb8
-rw-r--r--actionview/Rakefile43
-rw-r--r--actionview/app/assets/javascripts/config.coffee34
-rw-r--r--actionview/app/assets/javascripts/rails-ujs.coffee99
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/BANNER.js5
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee (renamed from actionview/app/assets/javascripts/features/confirm.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/disable.coffee (renamed from actionview/app/assets/javascripts/features/disable.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/method.coffee (renamed from actionview/app/assets/javascripts/features/method.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/remote.coffee (renamed from actionview/app/assets/javascripts/features/remote.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee70
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee (renamed from actionview/app/assets/javascripts/utils/ajax.coffee)7
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee (renamed from actionview/app/assets/javascripts/utils/csrf.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee (renamed from actionview/app/assets/javascripts/utils/dom.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee (renamed from actionview/app/assets/javascripts/utils/event.coffee)0
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/form.coffee (renamed from actionview/app/assets/javascripts/utils/form.coffee)0
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionview/package.json2
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/ujs/public/test/call-remote.js28
-rw-r--r--actionview/test/ujs/public/test/data-remote.js67
-rw-r--r--activemodel/CHANGELOG.md30
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb17
-rw-r--r--activemodel/lib/active_model/type/string.rb7
-rw-r--r--activemodel/lib/active_model/validations/format.rb1
-rw-r--r--activemodel/lib/active_model/validations/presence.rb1
-rw-r--r--activemodel/test/cases/errors_test.rb16
-rw-r--r--activemodel/test/cases/type/string_test.rb13
-rw-r--r--activemodel/test/validators/email_validator.rb1
-rw-r--r--activerecord/CHANGELOG.md6
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb69
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb88
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb105
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb36
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb1
-rw-r--r--activerecord/lib/active_record/fixtures.rb29
-rw-r--r--activerecord/lib/active_record/railtie.rb8
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/cache_key_test.rb2
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb1
-rw-r--r--activerecord/test/cases/column_definition_test.rb2
-rw-r--r--activerecord/test/cases/comment_test.rb14
-rw-r--r--activerecord/test/cases/dup_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb28
-rw-r--r--activerecord/test/cases/fixtures_test.rb18
-rw-r--r--activerecord/test/cases/integration_test.rb1
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb2
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb5
-rw-r--r--activerecord/test/cases/relations_test.rb6
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb2
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/models/essay.rb1
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb8
-rw-r--r--activesupport/CHANGELOG.md6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb32
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb5
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb15
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb40
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb17
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb717
-rw-r--r--activesupport/test/core_ext/module_test.rb32
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb12
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb745
-rw-r--r--activesupport/test/inflector_test.rb13
-rw-r--r--activesupport/test/inflector_test_cases.rb15
-rw-r--r--activesupport/test/time_zone_test.rb4
-rw-r--r--guides/assets/stylesheets/main.css4
-rw-r--r--guides/rails_guides/markdown.rb4
-rw-r--r--guides/source/5_1_release_notes.md57
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/active_record_callbacks.md10
-rw-r--r--guides/source/active_record_querying.md16
-rw-r--r--guides/source/api_documentation_guidelines.md4
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md26
-rw-r--r--guides/source/engines.md112
-rw-r--r--guides/source/rails_on_rack.md11
-rw-r--r--guides/source/routing.md15
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md8
-rw-r--r--railties/lib/rails.rb4
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb2
-rw-r--r--railties/lib/rails/code_statistics.rb3
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt1
-rw-r--r--railties/lib/rails/paths.rb9
-rw-r--r--railties/test/application/initializers/frameworks_test.rb8
-rw-r--r--railties/test/application/rake/migrations_test.rb6
-rw-r--r--railties/test/application/test_runner_test.rb2
-rw-r--r--railties/test/paths_test.rb20
135 files changed, 2106 insertions, 1294 deletions
diff --git a/.github/stale.yml b/.github/stale.yml
index 387b8fea9c..71704b3cd7 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,5 +1,7 @@
# Number of days of inactivity before an issue becomes stale
-days: 90
+daysUntilStale: 90
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
@@ -22,3 +24,5 @@ markComment: >
Thank you for all your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+# Limit to only `issues` or `pulls`
+only: issues
diff --git a/.travis.yml b/.travis.yml
index ed4e0fba3b..5b8cfa9dbf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -55,8 +55,8 @@ env:
- "GEM=ac:integration"
rvm:
- - 2.2.6
- - 2.3.3
+ - 2.2.7
+ - 2.3.4
- 2.4.1
- ruby-head
@@ -64,13 +64,13 @@ matrix:
include:
- rvm: 2.4.1
env: "GEM=av:ujs"
- - rvm: 2.2.6
+ - rvm: 2.2.7
env: "GEM=aj:integration"
services:
- memcached
- redis
- rabbitmq
- - rvm: 2.3.3
+ - rvm: 2.3.4
env: "GEM=aj:integration"
services:
- memcached
@@ -88,14 +88,19 @@ matrix:
- memcached
- redis
- rabbitmq
- - rvm: 2.3.3
+ - rvm: 2.3.4
env:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.0
- - rvm: 2.4.1
+ - rvm: 2.3.4
env:
- "GEM=ar:sqlite3_mem"
+ - rvm: 2.3.4
+ env:
+ - "GEM=ar:postgresql POSTGRES=9.2"
+ addons:
+ postgresql: "9.2"
- rvm: jruby-9.1.8.0
jdk: oraclejdk8
env:
@@ -104,12 +109,6 @@ matrix:
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
- # Test with old (< 9.4.2) postgresql
- - rvm: 2.4.0
- env:
- - "GEM=ar:postgresql"
- addons:
- postgresql: "9.4"
allow_failures:
- rvm: ruby-head
- rvm: jruby-9.1.8.0
diff --git a/Gemfile.lock b/Gemfile.lock
index 0c9337bf2a..4feb8e2968 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -433,4 +433,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.14.5
+ 1.14.6
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 98a114a5f4..30ac1e9c38 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -68,12 +68,33 @@ class ClientTest < ActionCable::TestCase
server.min_threads = 1
server.max_threads = 4
- t = Thread.new { server.run.join }
- yield port
+ thread = server.run
- ensure
- server.stop(true) if server
- t.join if t
+ begin
+ yield port
+
+ ensure
+ server.stop
+
+ begin
+ thread.join
+
+ rescue IOError
+ # Work around https://bugs.ruby-lang.org/issues/13405
+ #
+ # Puma's sometimes raising while shutting down, when it closes
+ # its internal pipe. We can safely ignore that, but we do need
+ # to do the step skipped by the exception:
+ server.binder.close
+
+ rescue RuntimeError => ex
+ # Work around https://bugs.ruby-lang.org/issues/13239
+ raise unless ex.message =~ /can't modify frozen IOError/
+
+ # Handle this as if it were the IOError: do the same as above.
+ server.binder.close
+ end
+ end
end
class SyncClient
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
index 914ed12312..e988fffcb9 100644
--- a/actionmailer/test/parameterized_test.rb
+++ b/actionmailer/test/parameterized_test.rb
@@ -14,7 +14,6 @@ class ParameterizedTest < ActiveSupport::TestCase
@previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
ActionMailer::Base.deliver_later_queue_name = :test_queue
- ActionMailer::Base.delivery_method = :test
@mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index c5b679207a..51cec82801 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1 +1,13 @@
+* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
+
+ `ActionController::Base` and `ActionController::API` have differing implementations. This means that
+ the one umbrella hook `action_controller` is not able to address certain situations where a method
+ may not exist in a certain implementation.
+
+ This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API`
+
+ Fixes #27013.
+
+ *Julian Nadeau*
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 0d1af0d0bd..94698df730 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -141,6 +141,7 @@ module ActionController
include mod
end
+ ActiveSupport.run_load_hooks(:action_controller_api, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 0fe0853da3..8c2b111f89 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -261,6 +261,13 @@ module ActionController
PROTECTED_IVARS
end
+ def self.make_response!(request)
+ ActionDispatch::Response.create.tap do |res|
+ res.request = request
+ end
+ end
+
+ ActiveSupport.run_load_hooks(:action_controller_base, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 74c4153cd2..246644dcbd 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -129,7 +129,7 @@ module ActionController
end
def self.make_response!(request)
- ActionDispatch::Response.create.tap do |res|
+ ActionDispatch::Response.new.tap do |res|
res.request = request
end
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index d9a8b9c12d..5051c02a62 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -213,7 +213,11 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity."
+ if valid_request_origin?
+ logger.warn "Can't verify CSRF token authenticity."
+ else
+ logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
+ end
end
handle_unverified_request
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 1190e0ed69..baf15570d5 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -667,6 +667,7 @@ module ActionController
other_hash.to_h.merge(@parameters)
)
end
+ alias_method :with_defaults, :reverse_merge
# Returns current <tt>ActionController::Parameters</tt> instance with
# current hash merged into +other_hash+.
@@ -674,6 +675,7 @@ module ActionController
@parameters.merge!(other_hash.to_h) { |key, left, right| left }
self
end
+ alias_method :with_defaults!, :reverse_merge!
# This is required by ActiveModel attribute assignment, so that user can
# pass +Parameters+ to a mass assignment methods in a model. It should not
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 9cc3d0757f..98fdb36c91 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -87,7 +87,7 @@ module ActionDispatch
def initialize(*) # :nodoc:
super
- self.class.superclass.driver.use
+ self.class.driver.use
end
def self.start_application # :nodoc:
@@ -100,6 +100,8 @@ module ActionDispatch
SystemTesting::Server.new.run
end
+ class_attribute :driver, instance_accessor: false
+
# System Test configuration options
#
# The default settings are Selenium, using Chrome, with a screen size
@@ -113,13 +115,10 @@ module ActionDispatch
#
# driven_by :selenium, screen_size: [800, 800]
def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
- @driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
+ self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
end
- # Returns the driver object for the initialized system test
- def self.driver
- @driver ||= SystemTestCase.driven_by(:selenium)
- end
+ driven_by :selenium
end
SystemTestCase.start_application
diff --git a/actionpack/test/controller/api/with_helpers_test.rb b/actionpack/test/controller/api/with_helpers_test.rb
index 9882a25ae1..06db949153 100644
--- a/actionpack/test/controller/api/with_helpers_test.rb
+++ b/actionpack/test/controller/api/with_helpers_test.rb
@@ -15,6 +15,12 @@ class WithHelpersController < ActionController::API
end
end
+class SubclassWithHelpersController < WithHelpersController
+ def with_helpers
+ render plain: self.class.helpers.my_helper
+ end
+end
+
class WithHelpersTest < ActionController::TestCase
tests WithHelpersController
@@ -24,3 +30,13 @@ class WithHelpersTest < ActionController::TestCase
assert_equal "helper", response.body
end
end
+
+class SubclassWithHelpersTest < ActionController::TestCase
+ tests WithHelpersController
+
+ def test_with_helpers
+ get :with_helpers
+
+ assert_equal "helper", response.body
+ end
+end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 42a5157010..4e969fac07 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -11,6 +11,12 @@ end
class EmptyController < ActionController::Base
end
+class SimpleController < ActionController::Base
+ def hello
+ self.response_body = "hello"
+ end
+end
+
class NonEmptyController < ActionController::Base
def public_action
head :ok
@@ -118,6 +124,27 @@ class ControllerInstanceTests < ActiveSupport::TestCase
controller = klass.new
assert_equal "examples", controller.controller_path
end
+
+ def test_response_has_default_headers
+ original_default_headers = ActionDispatch::Response.default_headers
+
+ ActionDispatch::Response.default_headers = {
+ "X-Frame-Options" => "DENY",
+ "X-Content-Type-Options" => "nosniff",
+ "X-XSS-Protection" => "1;"
+ }
+
+ response_headers = SimpleController.action("hello").call(
+ "REQUEST_METHOD" => "GET",
+ "rack.input" => -> {}
+ )[1]
+
+ assert response_headers.key?("X-Frame-Options")
+ assert response_headers.key?("X-Content-Type-Options")
+ assert response_headers.key?("X-XSS-Protection")
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
end
class PerformActionTest < ActionController::TestCase
diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb
new file mode 100644
index 0000000000..e16452ed6f
--- /dev/null
+++ b/actionpack/test/controller/metal_test.rb
@@ -0,0 +1,30 @@
+require "abstract_unit"
+
+class MetalControllerInstanceTests < ActiveSupport::TestCase
+ class SimpleController < ActionController::Metal
+ def hello
+ self.response_body = "hello"
+ end
+ end
+
+ def test_response_has_default_headers
+ original_default_headers = ActionDispatch::Response.default_headers
+
+ ActionDispatch::Response.default_headers = {
+ "X-Frame-Options" => "DENY",
+ "X-Content-Type-Options" => "nosniff",
+ "X-XSS-Protection" => "1;"
+ }
+
+ response_headers = SimpleController.action("hello").call(
+ "REQUEST_METHOD" => "GET",
+ "rack.input" => -> {}
+ )[1]
+
+ refute response_headers.key?("X-Frame-Options")
+ refute response_headers.key?("X-Content-Type-Options")
+ refute response_headers.key?("X-XSS-Protection")
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
+end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 9f3025587e..3e067314d6 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -310,6 +310,14 @@ class ParametersPermitTest < ActiveSupport::TestCase
refute_predicate merged_params[:person], :empty?
end
+ test "#with_defaults is an alias of reverse_merge" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ merged_params = @params.with_defaults(default_params)
+
+ assert_equal "1234", merged_params[:id]
+ refute_predicate merged_params[:person], :empty?
+ end
+
test "not permitted is sticky beyond reverse_merge" do
refute_predicate @params.reverse_merge(a: "b"), :permitted?
end
@@ -327,6 +335,14 @@ class ParametersPermitTest < ActiveSupport::TestCase
refute_predicate @params[:person], :empty?
end
+ test "#with_defaults! is an alias of reverse_merge!" do
+ default_params = ActionController::Parameters.new(id: "1234", person: {}).permit!
+ @params.with_defaults!(default_params)
+
+ assert_equal "1234", @params[:id]
+ refute_predicate @params[:person], :empty?
+ end
+
test "modifying the parameters" do
@params[:person][:hometown] = "Chicago"
@params[:person][:family] = { brother: "Jonas" }
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 1eb92abae4..2a41d57b26 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -32,13 +32,13 @@ class ParamsWrapperTest < ActionController::TestCase
def self.attribute_names
[]
end
- end
- class Person
- def self.stores_attributes
+ def self.stored_attributes
{ settings: [:color, :size] }
end
+ end
+ class Person
def self.attribute_names
[]
end
@@ -67,11 +67,13 @@ class ParamsWrapperTest < ActionController::TestCase
end
def test_store_accessors_wrapped
- with_default_wrapper_options do
- @request.env["CONTENT_TYPE"] = "application/json"
- post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" }
- assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large",
- "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" })
+ assert_called(User, :attribute_names, times: 2, returns: ["username"]) do
+ with_default_wrapper_options do
+ @request.env["CONTENT_TYPE"] = "application/json"
+ post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" }
+ assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large",
+ "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" })
+ end
end
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index d645ddfdbe..794e057b85 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -347,6 +347,10 @@ module RequestForgeryProtectionTests
end
def test_should_block_post_with_origin_checking_and_wrong_origin
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
forgery_protection_origin_check do
session[:_csrf_token] = @token
@controller.stub :form_authenticity_token, @token do
@@ -356,6 +360,13 @@ module RequestForgeryProtectionTests
end
end
end
+
+ assert_match(
+ "HTTP Origin header (http://bad.host) didn't match request.base_url (http://test.host)",
+ logger.logged(:warn).last
+ )
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_should_warn_on_missing_csrf_token
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index 1a9421c098..33d98f924f 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -6,6 +6,14 @@ class SetDriverToRackTestTest < DrivenByRackTest
end
end
+class OverrideSeleniumSubclassToRackTestTest < DrivenBySeleniumWithChrome
+ driven_by :rack_test
+
+ test "uses rack_test" do
+ assert_equal :rack_test, Capybara.current_driver
+ end
+end
+
class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome
test "uses selenium" do
assert_equal :selenium, Capybara.current_driver
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 00ab92129d..de588ad25c 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -1,10 +1,13 @@
require "rake/testtask"
require "fileutils"
+require "open3"
+
+dir = File.dirname(__FILE__)
desc "Default Task"
task default: :test
-task package: "assets:compile"
+task package: %w( assets:compile assets:verify )
# Run the unit tests
@@ -84,8 +87,46 @@ namespace :assets do
desc "Compile Action View assets"
task :compile do
require "blade"
+ require "sprockets"
+ require "sprockets/export"
Blade.build
end
+
+ desc "Verify compiled Action View assets"
+ task :verify do
+ file = "lib/assets/compiled/rails-ujs.js"
+ pathname = Pathname.new("#{dir}/#{file}")
+
+ print "[verify] #{file} exists "
+ if pathname.exist?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{file} is a UMD module "
+ if pathname.read =~ /module\.exports.*define\.amd/m
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]"
+ fail
+ end
+
+ print "[verify] #{dir} can be required as a module "
+ js = <<-JS
+ window = { Event: class {} }
+ class Element {}
+ require('#{dir}')
+ JS
+ stdout, stderr, status = Open3.capture3("node", "--print", js)
+ if status.success?
+ puts "[OK]"
+ else
+ $stderr.puts "[FAIL]\n#{stderr}"
+ fail
+ end
+ end
end
task :lines do
diff --git a/actionview/app/assets/javascripts/config.coffee b/actionview/app/assets/javascripts/config.coffee
deleted file mode 100644
index a93325e903..0000000000
--- a/actionview/app/assets/javascripts/config.coffee
+++ /dev/null
@@ -1,34 +0,0 @@
-#= export Rails
-
-@Rails =
- # Link elements bound by rails-ujs
- linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
-
- # Button elements bound by rails-ujs
- buttonClickSelector:
- selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
- exclude: 'form button'
-
- # Select elements bound by rails-ujs
- inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
-
- # Form elements bound by rails-ujs
- formSubmitSelector: 'form'
-
- # Form input elements bound by rails-ujs
- formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
-
- # Form input elements disabled during form submission
- formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled'
-
- # Form input elements re-enabled after form submission
- formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
-
- # Form file input elements
- fileInputSelector: 'input[name][type=file]:not([disabled])'
-
- # Link onClick disable selector with possible reenable after remote submission
- linkDisableSelector: 'a[data-disable-with], a[data-disable]'
-
- # Button onClick disable selector with possible reenable after remote submission
- buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee
index afe7d6f7a3..bd6e9bb881 100644
--- a/actionview/app/assets/javascripts/rails-ujs.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs.coffee
@@ -1,80 +1,39 @@
-#
-# Unobtrusive JavaScript
-# https://github.com/rails/rails-ujs
-#
-# Released under the MIT license
-#
-#= require ./config
-#= require_tree ./utils
-#= require_tree ./features
+#= require ./rails-ujs/BANNER
+#= export Rails
+#= require_self
+#= require_tree ./rails-ujs/utils
+#= require_tree ./rails-ujs/features
+#= require ./rails-ujs/start
-{
- fire, delegate
- getData, $
- refreshCSRFTokens, CSRFProtection
- enableElement, disableElement, handleDisabledElement
- handleConfirm
- handleRemote, formSubmitButtonClick, handleMetaClick
- handleMethod
-} = Rails
+@Rails =
+ # Link elements bound by rails-ujs
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
-# For backward compatibility
-if jQuery? and not jQuery.rails
- jQuery.rails = Rails
- jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
- CSRFProtection(xhr) unless options.crossDomain
+ # Button elements bound by rails-ujs
+ buttonClickSelector:
+ selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
+ exclude: 'form button'
-Rails.start = ->
- # Cut down on the number of issues from people inadvertently including
- # rails-ujs twice by detecting and raising an error when it happens.
- throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
+ # Select elements bound by rails-ujs
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
- # This event works the same as the load event, except that it fires every
- # time the page is loaded.
- # See https://github.com/rails/jquery-ujs/issues/357
- # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
- window.addEventListener 'pageshow', ->
- $(Rails.formEnableSelector).forEach (el) ->
- enableElement(el) if getData(el, 'ujs:disabled')
- $(Rails.linkDisableSelector).forEach (el) ->
- enableElement(el) if getData(el, 'ujs:disabled')
+ # Form elements bound by rails-ujs
+ formSubmitSelector: 'form'
- delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
- delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
- delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
- delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+ # Form input elements bound by rails-ujs
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
- delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
- delegate document, Rails.linkClickSelector, 'click', handleConfirm
- delegate document, Rails.linkClickSelector, 'click', handleMetaClick
- delegate document, Rails.linkClickSelector, 'click', disableElement
- delegate document, Rails.linkClickSelector, 'click', handleRemote
- delegate document, Rails.linkClickSelector, 'click', handleMethod
+ # Form input elements disabled during form submission
+ formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled'
- delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
- delegate document, Rails.buttonClickSelector, 'click', handleConfirm
- delegate document, Rails.buttonClickSelector, 'click', disableElement
- delegate document, Rails.buttonClickSelector, 'click', handleRemote
+ # Form input elements re-enabled after form submission
+ formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
- delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement
- delegate document, Rails.inputChangeSelector, 'change', handleConfirm
- delegate document, Rails.inputChangeSelector, 'change', handleRemote
+ # Form file input elements
+ fileInputSelector: 'input[name][type=file]:not([disabled])'
- delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement
- delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
- delegate document, Rails.formSubmitSelector, 'submit', handleRemote
- # Normal mode submit
- # Slight timeout so that the submit button gets properly serialized
- delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
- delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
- delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+ # Link onClick disable selector with possible reenable after remote submission
+ linkDisableSelector: 'a[data-disable-with], a[data-disable]'
- delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
- delegate document, Rails.formInputClickSelector, 'click', handleConfirm
- delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
-
- document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
- window._rails_loaded = true
-
-if window.Rails is Rails and fire(document, 'rails:attachBindings')
- Rails.start()
+ # Button onClick disable selector with possible reenable after remote submission
+ buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/rails-ujs/BANNER.js b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
new file mode 100644
index 0000000000..47ecd66003
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
@@ -0,0 +1,5 @@
+/*
+Unobtrusive JavaScript
+https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
+Released under the MIT license
+ */
diff --git a/actionview/app/assets/javascripts/features/confirm.coffee b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
index 72b5aaa218..72b5aaa218 100644
--- a/actionview/app/assets/javascripts/features/confirm.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
diff --git a/actionview/app/assets/javascripts/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
index 90aa3bdf0e..90aa3bdf0e 100644
--- a/actionview/app/assets/javascripts/features/disable.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
diff --git a/actionview/app/assets/javascripts/features/method.coffee b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
index d04d9414dd..d04d9414dd 100644
--- a/actionview/app/assets/javascripts/features/method.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
diff --git a/actionview/app/assets/javascripts/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
index 852587042c..852587042c 100644
--- a/actionview/app/assets/javascripts/features/remote.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
new file mode 100644
index 0000000000..5746a22287
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -0,0 +1,70 @@
+{
+ fire, delegate
+ getData, $
+ refreshCSRFTokens, CSRFProtection
+ enableElement, disableElement, handleDisabledElement
+ handleConfirm
+ handleRemote, formSubmitButtonClick, handleMetaClick
+ handleMethod
+} = Rails
+
+# For backward compatibility
+if jQuery? and not jQuery.rails
+ jQuery.rails = Rails
+ jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
+ CSRFProtection(xhr) unless options.crossDomain
+
+Rails.start = ->
+ # Cut down on the number of issues from people inadvertently including
+ # rails-ujs twice by detecting and raising an error when it happens.
+ throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
+
+ # This event works the same as the load event, except that it fires every
+ # time the page is loaded.
+ # See https://github.com/rails/jquery-ujs/issues/357
+ # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
+ window.addEventListener 'pageshow', ->
+ $(Rails.formEnableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+ $(Rails.linkDisableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+
+ delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+
+ delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.linkClickSelector, 'click', handleConfirm
+ delegate document, Rails.linkClickSelector, 'click', handleMetaClick
+ delegate document, Rails.linkClickSelector, 'click', disableElement
+ delegate document, Rails.linkClickSelector, 'click', handleRemote
+ delegate document, Rails.linkClickSelector, 'click', handleMethod
+
+ delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.buttonClickSelector, 'click', handleConfirm
+ delegate document, Rails.buttonClickSelector, 'click', disableElement
+ delegate document, Rails.buttonClickSelector, 'click', handleRemote
+
+ delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement
+ delegate document, Rails.inputChangeSelector, 'change', handleConfirm
+ delegate document, Rails.inputChangeSelector, 'change', handleRemote
+
+ delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement
+ delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
+ delegate document, Rails.formSubmitSelector, 'submit', handleRemote
+ # Normal mode submit
+ # Slight timeout so that the submit button gets properly serialized
+ delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
+ delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
+ delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+
+ delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.formInputClickSelector, 'click', handleConfirm
+ delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
+
+ document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
+ window._rails_loaded = true
+
+if window.Rails is Rails and fire(document, 'rails:attachBindings')
+ Rails.start()
diff --git a/actionview/app/assets/javascripts/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index 9af515beda..26df7b9a3f 100644
--- a/actionview/app/assets/javascripts/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -29,6 +29,7 @@ Rails.ajax = (options) ->
fire(document, 'ajaxStop') # to be compatible with jQuery.ajax
prepareOptions = (options) ->
+ options.url = options.url or location.href
options.type = options.type.toUpperCase()
# append data to url if it's a GET request
if options.type is 'GET' and options.data
@@ -63,10 +64,10 @@ processResponse = (response, type) ->
if typeof response is 'string' and typeof type is 'string'
if type.match(/\bjson\b/)
try response = JSON.parse(response)
- else if type.match(/\bjavascript\b/)
+ else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
- script.innerHTML = response
- document.body.appendChild(script)
+ script.text = response
+ document.head.appendChild(script).parentNode.removeChild(script)
else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
diff --git a/actionview/app/assets/javascripts/utils/csrf.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
index 4eb5ebb414..4eb5ebb414 100644
--- a/actionview/app/assets/javascripts/utils/csrf.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
diff --git a/actionview/app/assets/javascripts/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
index 6bef618147..6bef618147 100644
--- a/actionview/app/assets/javascripts/utils/dom.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
index 8d3ff007ea..8d3ff007ea 100644
--- a/actionview/app/assets/javascripts/utils/event.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
diff --git a/actionview/app/assets/javascripts/utils/form.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
index 5fa337b518..5fa337b518 100644
--- a/actionview/app/assets/javascripts/utils/form.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index ba189e23fe..5ddf1ceb66 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -62,7 +62,7 @@ module ActionView
node
end
else
- unless name.include?('#') # Dynamic template partial names can never be tracked
+ unless name.include?("#") # Dynamic template partial names can never be tracked
logger.error " Couldn't find template for digesting: #{name}"
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 26a625e4fe..1a419508e5 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -416,13 +416,13 @@ module ActionView
#
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
#
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
# ...
# <% end %>
#
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
#
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
# ...
# <% end %>
def form_for(record, options = {}, &block)
diff --git a/actionview/package.json b/actionview/package.json
index 690749a051..85f4ddacbe 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -11,7 +11,7 @@
},
"scripts": {
"build": "bundle exec blade build",
- "test": "echo \"See the README: https://github.com/rails/rails-ujs#how-to-run-tests\" && exit 1",
+ "test": "echo \"See the README: https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts#how-to-run-tests\" && exit 1",
"lint": "coffeelint app/assets/javascripts && eslint test/public/test"
},
"repository": {
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index 11ed55456f..4d4ed3c35c 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-# The exhaustive tests are in test/controller/html/sanitizer_test.rb.
+# The exhaustive tests are in the rails-html-sanitizer gem.
# This tests that the helpers hook up correctly to the sanitizer classes.
class SanitizeHelperTest < ActionView::TestCase
tests ActionView::Helpers::SanitizeHelper
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
index dbeb8ad832..5932195363 100644
--- a/actionview/test/ujs/public/test/call-remote.js
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -100,6 +100,34 @@ asyncTest('JS code should be executed', 1, function() {
submit()
})
+asyncTest('ecmascript code should be executed', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="application/ecmascript">')
+ $('form').append('<input type="text" name="content" value="ok(true, \'remote code should be run\')">')
+
+ submit()
+})
+
+asyncTest('execution of JS code does not modify current DOM', 1, function() {
+ var docLength, newDocLength
+ function getDocLength() {
+ return document.documentElement.outerHTML.length
+ }
+
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="text/javascript">')
+ $('form').append('<input type="text" name="content" value="\'remote code should be run\'">')
+
+ docLength = getDocLength()
+
+ submit(function() {
+ newDocLength = getDocLength()
+ ok(docLength === newDocLength, 'executed JS should not present in the document')
+ })
+})
+
asyncTest('XML document should be parsed', 1, function() {
buildForm({ method: 'post', 'data-type': 'html' })
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index b756add24e..161a92ac11 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -1,3 +1,17 @@
+(function() {
+
+function buildSelect(attrs) {
+ attrs = $.extend({
+ 'name': 'user_data', 'data-remote': 'true', 'data-url': '/echo', 'data-params': 'data1=value1'
+ }, attrs)
+
+ $('#qunit-fixture').append(
+ $('<select />', attrs)
+ .append($('<option />', {value: 'optionValue1', text: 'option1'}))
+ .append($('<option />', {value: 'optionValue2', text: 'option2'}))
+ )
+}
+
module('data-remote', {
setup: function() {
$('#qunit-fixture')
@@ -135,17 +149,7 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() {
})
asyncTest('changing a select option with data-remote attribute', 5, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'true',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect()
$('select[data-remote]')
.bindNative('ajax:success', function(e, data, status, xhr) {
@@ -350,17 +354,7 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function()
})
asyncTest('changing a select option with falsy "data-remote" attribute', 0, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'false',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect({'data-remote': 'false'})
$('select[data-remote=false]:first')
.bindNative('ajax:beforeSend', function() {
@@ -413,3 +407,32 @@ asyncTest('form buttons should only be serialized when clicked', 4, function() {
})
.find('[name=submit2]').triggerNative('click')
})
+
+asyncTest('changing a select option without "data-url" attribute still fires ajax request to current location', 1, function() {
+ var currentLocation, ajaxLocation
+
+ buildSelect({'data-url': ''});
+
+ $('select[data-remote]')
+ .bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ // Get current location (the same way jQuery does)
+ try {
+ currentLocation = location.href
+ } catch(err) {
+ currentLocation = document.createElement( 'a' )
+ currentLocation.href = ''
+ currentLocation = currentLocation.href
+ }
+
+ ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
+ equal(ajaxLocation, currentLocation, 'URL should be current page by default')
+
+ return false
+ })
+ .val('optionValue2')
+ .triggerNative('change')
+
+ setTimeout(function() { start() }, 20)
+})
+
+})()
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 6b9b3dd7ff..cdba0cee12 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,32 @@
-* Avoid converting integer as a string into float.
+* Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
- *namusyaka*
+ Change `#keys` to only return the keys that don't have empty messages.
+
+ Change `#values` to only return the not empty values.
+
+ Example:
+
+ # Before
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => [:name]
+ person.errors.values # => [[]]
+
+ # After
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => []
+ person.errors.values # => []
+
+ *bogdanvlviv*
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
index 7dad3b6dff..ee130df989 100644
--- a/activemodel/lib/active_model/attribute_assignment.rb
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -27,7 +27,7 @@ module ActiveModel
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
- return if new_attributes.nil? || new_attributes.empty?
+ return if new_attributes.empty?
attributes = new_attributes.stringify_keys
_assign_attributes(sanitize_for_mass_assignment(attributes))
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 9df4ca51fe..942b4fa9bb 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -132,15 +132,6 @@ module ActiveModel
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
- #
- # Note that, if you try to get errors of an attribute which has
- # no errors associated with it, this method will instantiate
- # an empty error list for it and +keys+ will return an array
- # of error keys which includes this attribute.
- #
- # person.errors.keys # => []
- # person.errors[:name] # => []
- # person.errors.keys # => [:name]
def [](attribute)
messages[attribute.to_sym]
end
@@ -181,7 +172,9 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.values # => [["cannot be nil", "must be specified"]]
def values
- messages.values
+ messages.select do |key, value|
+ !value.empty?
+ end.values
end
# Returns all message keys.
@@ -189,7 +182,9 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.keys # => [:name]
def keys
- messages.keys
+ messages.select do |key, value|
+ !value.empty?
+ end.keys
end
# Returns +true+ if no errors are found, +false+ otherwise.
diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb
index c7e0208a5a..850cab962b 100644
--- a/activemodel/lib/active_model/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -12,7 +12,12 @@ module ActiveModel
private
def cast_value(value)
- ::String.new(super)
+ case value
+ when ::String then ::String.new(value)
+ when true then "t".freeze
+ when false then "f".freeze
+ else value.to_s
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index b4b8d9f33c..98b3f6a8e5 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class FormatValidator < EachValidator # :nodoc:
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 6e8a434bbe..ce4106a5e1 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class PresenceValidator < EachValidator # :nodoc:
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 0872084cf5..43aee5a814 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -99,6 +99,14 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["omg", "zomg"], errors.values
end
+ test "values returns an empty array after try to get a message only" do
+ errors = ActiveModel::Errors.new(self)
+ errors.messages[:foo]
+ errors.messages[:baz]
+
+ assert_equal [], errors.values
+ end
+
test "keys returns the error keys" do
errors = ActiveModel::Errors.new(self)
errors.messages[:foo] << "omg"
@@ -107,6 +115,14 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal [:foo, :baz], errors.keys
end
+ test "keys returns an empty array after try to get a message only" do
+ errors = ActiveModel::Errors.new(self)
+ errors.messages[:foo]
+ errors.messages[:baz]
+
+ assert_equal [], errors.keys
+ end
+
test "detecting whether there are errors with empty?, blank?, include?" do
person = Person.new
person.errors[:foo]
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
index 222083817e..47d412e27e 100644
--- a/activemodel/test/cases/type/string_test.rb
+++ b/activemodel/test/cases/type/string_test.rb
@@ -12,16 +12,25 @@ module ActiveModel
end
test "cast strings are mutable" do
- s = "foo"
type = Type::String.new
+
+ s = "foo"
assert_equal false, type.cast(s).frozen?
+ assert_equal false, s.frozen?
+
+ f = "foo".freeze
+ assert_equal false, type.cast(f).frozen?
+ assert_equal true, f.frozen?
end
test "values are duped coming out" do
- s = "foo"
type = Type::String.new
+
+ s = "foo"
assert_not_same s, type.cast(s)
+ assert_equal s, type.cast(s)
assert_not_same s, type.deserialize(s)
+ assert_equal s, type.deserialize(s)
end
end
end
diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb
index 9b74d83b37..f733c45cf0 100644
--- a/activemodel/test/validators/email_validator.rb
+++ b/activemodel/test/validators/email_validator.rb
@@ -1,4 +1,3 @@
-
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 30d580b9e3..9e3b686bbb 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1 +1,7 @@
+* When calling the dynamic fixture accessor method with no arguments it now returns all fixtures of this type.
+ Previously this method always returned an empty array.
+
+ *Kevin McPhillips*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 6efa448d49..e52c2004f3 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1649,7 +1649,7 @@ module ActiveRecord
# you don't want to have association presence validated, use <tt>optional: true</tt>.
# [:default]
# Provide a callable (i.e. proc or lambda) to specify that the association should
- # be initialized with a particular record before validation.
+ # be initialized with a particular record before validation.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 6aa414ba6b..bd5003d63a 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -282,7 +282,7 @@ module ActiveRecord
#{attr_name} is not an attribute known to Active Record.
This behavior is deprecated and will be removed in the next
version of Rails. If you'd like #{attr_name} to be managed
- by Active Record, add `attribute :#{attr_name} to your class.
+ by Active Record, add `attribute :#{attr_name}` to your class.
EOW
mutations_from_database.deprecated_force_change(attr_name)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 3f2e86a98d..53dbbd8c21 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -825,7 +825,7 @@ module ActiveRecord
# end
#
# class Book < ActiveRecord::Base
- # establish_connection "library_db"
+ # establish_connection :library_db
# end
#
# class ScaryBook < Book
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index cce8883076..f0c0fbab6c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -61,17 +61,6 @@ module ActiveRecord
lookup_cast_type(column.sql_type)
end
- def fetch_type_metadata(sql_type)
- cast_type = lookup_cast_type(sql_type)
- SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- end
-
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
@@ -161,6 +150,10 @@ module ActiveRecord
end
private
+ def lookup_cast_type(sql_type)
+ type_map.lookup(sql_type)
+ end
+
def id_value_for_database(value)
if primary_key = value.class.primary_key
value.instance_variable_get(:@attributes)[primary_key].value_for_database
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index c48a4acff8..a4fecc4a8e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -133,5 +133,6 @@ module ActiveRecord
end
end
end
+ SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc:
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 1cc10e3b93..13629dee7f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -976,16 +976,6 @@ module ActiveRecord
foreign_key_for(from_table, options_or_to_table).present?
end
- def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
- return unless supports_foreign_keys?
- foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
- end
-
- def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
- foreign_key_for(from_table, options_or_to_table) || \
- raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
- end
-
def foreign_key_column_for(table_name) # :nodoc:
prefix = Base.table_name_prefix
suffix = Base.table_name_suffix
@@ -1005,19 +995,6 @@ module ActiveRecord
insert_versions_sql(versions)
end
- def insert_versions_sql(versions) # :nodoc:
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
-
- if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n"
- sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
- sql << ";\n\n"
- sql
- else
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
- end
- end
-
def initialize_schema_migrations_table # :nodoc:
ActiveRecord::SchemaMigration.create_table
end
@@ -1263,6 +1240,10 @@ module ActiveRecord
end
end
+ def schema_creation
+ SchemaCreation.new(self)
+ end
+
def create_table_definition(*args)
TableDefinition.new(*args)
end
@@ -1271,6 +1252,17 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def fetch_type_metadata(sql_type)
+ cast_type = lookup_cast_type(sql_type)
+ SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ end
+
def index_column_names(column_names)
if column_names.is_a?(String) && /\W/.match?(column_names)
column_names
@@ -1295,6 +1287,24 @@ module ActiveRecord
end
end
+ def foreign_key_for(from_table, options_or_to_table = {})
+ return unless supports_foreign_keys?
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
+ end
+
+ def foreign_key_for!(from_table, options_or_to_table = {})
+ foreign_key_for(from_table, options_or_to_table) || \
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "CASCADE"; :cascade
+ when "SET NULL"; :nullify
+ when "RESTRICT"; :restrict
+ end
+ end
+
def validate_index_length!(table_name, new_name, internal = false)
max_index_length = internal ? index_name_length : allowed_index_name_length
@@ -1315,6 +1325,19 @@ module ActiveRecord
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
+ def insert_versions_sql(versions)
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
+
+ if versions.is_a?(Array)
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
+ sql << ";\n\n"
+ sql
+ else
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
+ end
+ end
+
def data_source_sql(name = nil, type: nil)
raise NotImplementedError
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6bb072dd73..19b7821494 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -149,57 +149,67 @@ module ActiveRecord
end
def begin_transaction(options = {})
- run_commit_callbacks = !current_transaction.joinable?
- transaction =
- if @stack.empty?
- RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
- else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
- run_commit_callbacks: run_commit_callbacks)
- end
+ @connection.lock.synchronize do
+ run_commit_callbacks = !current_transaction.joinable?
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
+ run_commit_callbacks: run_commit_callbacks)
+ end
- @stack.push(transaction)
- transaction
+ @stack.push(transaction)
+ transaction
+ end
end
def commit_transaction
- transaction = @stack.last
+ @connection.lock.synchronize do
+ transaction = @stack.last
- begin
- transaction.before_commit_records
- ensure
- @stack.pop
- end
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
- transaction.commit
- transaction.commit_records
+ transaction.commit
+ transaction.commit_records
+ end
end
def rollback_transaction(transaction = nil)
- transaction ||= @stack.pop
- transaction.rollback
- transaction.rollback_records
+ @connection.lock.synchronize do
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
+ end
end
def within_new_transaction(options = {})
- transaction = begin_transaction options
- yield
- rescue Exception => error
- if transaction
- rollback_transaction
- after_failure_actions(transaction, error)
- end
- raise
- ensure
- unless error
- if Thread.current.status == "aborting"
- rollback_transaction if transaction
- else
- begin
- commit_transaction
- rescue Exception
- rollback_transaction(transaction) unless transaction.state.completed?
- raise
+ @connection.lock.synchronize do
+ begin
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
+ raise
+ ensure
+ unless error
+ if Thread.current.status == "aborting"
+ rollback_transaction if transaction
+ else
+ begin
+ commit_transaction
+ rescue Exception
+ rollback_transaction(transaction) unless transaction.state.completed?
+ raise
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 550b4cc86b..85d6fbe8b3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -74,7 +74,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :owner, :logger
+ attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
def self.type_cast_config_to_integer(config)
@@ -93,8 +93,6 @@ module ActiveRecord
end
end
- attr_reader :prepared_statements
-
def initialize(connection, logger = nil, config = {}) # :nodoc:
super()
@@ -142,26 +140,10 @@ module ActiveRecord
end
end
- def collector
- if prepared_statements
- SQLString.new
- else
- BindCollector.new
- end
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::ToSql.new(self)
- end
-
def valid_type?(type) # :nodoc:
!native_database_types[type].nil?
end
- def schema_creation
- SchemaCreation.new self
- end
-
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
@@ -475,14 +457,6 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
- Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
- end
-
- def lookup_cast_type(sql_type) # :nodoc:
- type_map.lookup(sql_type)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
visitor.accept(node, collector).value
end
@@ -629,6 +603,18 @@ module ActiveRecord
columns(table_name).detect { |c| c.name == column_name } ||
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
end
+
+ def collector
+ if prepared_statements
+ SQLString.new
+ else
+ BindCollector.new
+ end
+ end
+
+ def arel_visitor
+ Arel::Visitors::ToSql.new(self)
+ end
end
end
end
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 55ec112c17..f118e086bb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -22,14 +22,6 @@ module ActiveRecord
MySQL::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- MySQL::SchemaCreation.new(self)
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::MySQL.new(self)
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -171,10 +163,6 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(*args) #:nodoc:
- MySQL::Column.new(*args)
- end
-
# Must return the MySQL error number from the exception, if the exception has an
# error number.
def error_number(exception) # :nodoc:
@@ -346,16 +334,6 @@ module ActiveRecord
indexes
end
- def new_column_from_field(table_name, field) # :nodoc:
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
- end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
- end
-
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name)
@@ -658,10 +636,6 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, extra = "")
- MySQL::TypeMetadata.new(super(sql_type), extra: extra)
- end
-
def add_index_length(quoted_columns, **options)
if length = options[:length]
case length
@@ -864,19 +838,12 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- end
- end
-
def create_table_info(table_name) # :nodoc:
select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(*args) # :nodoc:
- MySQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::MySQL.new(self)
end
def mismatched_foreign_key(message)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index 10c8bd179a..9e2d0fb5e7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -3,6 +3,42 @@ module ActiveRecord
module MySQL
module SchemaStatements # :nodoc:
private
+ def schema_creation
+ MySQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ MySQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
+ end
+
+ MySQL::Column.new(
+ field[:Field],
+ default,
+ type_metadata,
+ field[:Null] == "YES",
+ table_name,
+ default_function,
+ field[:Collation],
+ comment: field[:Comment].presence
+ )
+ end
+
+ def fetch_type_metadata(sql_type, extra = "")
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
+ end
+
+ def extract_foreign_key_action(specifier)
+ super unless specifier == "RESTRICT"
+ end
+
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index e399b6e499..da8d0c6992 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -77,6 +77,9 @@ module ActiveRecord
end
private
+ def lookup_cast_type(sql_type)
+ super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
+ end
def _quote(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index a332375b78..02a6da2f71 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -144,25 +144,6 @@ module ActiveRecord
end.compact
end
- def new_column_from_field(table_name, field) # :nondoc:
- column_name, type, default, notnull, oid, fmod, collation, comment = field
- oid = oid.to_i
- fmod = fmod.to_i
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
- default_value = extract_value_from_default(default)
- default_function = extract_default_function(default_value, default)
- PostgreSQLColumn.new(
- column_name,
- default_value,
- type_metadata,
- !notnull,
- table_name,
- default_function,
- collation,
- comment: comment.presence
- )
- end
-
def table_options(table_name) # :nodoc:
if comment = table_comment(table_name)
{ comment: comment }
@@ -362,16 +343,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- scope = quoted_scope(table_name)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT column_name
- FROM information_schema.key_column_usage kcu
- JOIN information_schema.table_constraints tc
- USING (table_schema, table_name, constraint_name)
- WHERE constraint_type = 'PRIMARY KEY'
- AND kcu.table_name = #{scope[:name]}
- AND kcu.table_schema = #{scope[:schema]}
- ORDER BY kcu.ordinal_position
+ SELECT a.attname
+ FROM (
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
+ FROM pg_index
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
+ AND indisprimary
+ ) i
+ JOIN pg_attribute a
+ ON a.attrelid = i.indrelid
+ AND a.attnum = i.indkey[i.idx]
+ ORDER BY i.idx
SQL
end
@@ -538,14 +521,6 @@ module ActiveRecord
end
end
- def extract_foreign_key_action(specifier) # :nodoc:
- case specifier
- when "c"; :cascade
- when "n"; :nullify
- when "r"; :restrict
- end
- end
-
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
@@ -593,19 +568,53 @@ module ActiveRecord
[super, *order_columns].join(", ")
end
- def fetch_type_metadata(column_name, sql_type, oid, fmod)
- cast_type = get_oid_type(oid, fmod, column_name, sql_type)
- simple_type = SqlTypeMetadata.new(
- sql_type: sql_type,
- type: cast_type.type,
- limit: cast_type.limit,
- precision: cast_type.precision,
- scale: cast_type.scale,
- )
- PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
- end
-
private
+ def schema_creation
+ PostgreSQL::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ PostgreSQL::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
+ default_value = extract_value_from_default(default)
+ default_function = extract_default_function(default_value, default)
+
+ PostgreSQLColumn.new(
+ column_name,
+ default_value,
+ type_metadata,
+ !notnull,
+ table_name,
+ default_function,
+ collation,
+ comment: comment.presence
+ )
+ end
+
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
+ simple_type = SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
+ end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "c"; :cascade
+ when "n"; :nullify
+ when "r"; :restrict
+ end
+ end
+
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6273614f0c..7ae9bd9a5f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -121,14 +121,6 @@ module ActiveRecord
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- def schema_creation # :nodoc:
- PostgreSQL::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::PostgreSQL.new(self)
- end
-
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -247,7 +239,9 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query "SELECT 1"
+ @lock.synchronize do
+ @connection.query "SELECT 1"
+ end
true
rescue PG::Error
false
@@ -255,26 +249,32 @@ module ActiveRecord
# Close then reopen the connection.
def reconnect!
- super
- @connection.reset
- configure_connection
+ @lock.synchronize do
+ super
+ @connection.reset
+ configure_connection
+ end
end
def reset!
- clear_cache!
- reset_transaction
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
- @connection.query "ROLLBACK"
+ @lock.synchronize do
+ clear_cache!
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query "ROLLBACK"
+ end
+ @connection.query "DISCARD ALL"
+ configure_connection
end
- @connection.query "DISCARD ALL"
- configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- super
- @connection.close rescue nil
+ @lock.synchronize do
+ super
+ @connection.close rescue nil
+ end
end
def native_database_types #:nodoc:
@@ -382,11 +382,6 @@ module ActiveRecord
PostgreSQL::Table.new(table_name, base)
end
- def lookup_cast_type(sql_type) # :nodoc:
- oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i
- super(oid)
- end
-
def column_name_for_operation(operation, node) # :nodoc:
OPERATION_ALIASES.fetch(operation) { operation.downcase }
end
@@ -783,8 +778,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(*args)
- PostgreSQL::TableDefinition.new(*args)
+ def arel_visitor
+ Arel::Visitors::PostgreSQL.new(self)
end
def can_perform_case_insensitive_comparison_for?(column)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 4ba245a0d8..8066a05c5e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -3,6 +3,31 @@ module ActiveRecord
module SQLite3
module SchemaStatements # :nodoc:
private
+ def schema_creation
+ SQLite3::SchemaCreation.new(self)
+ end
+
+ def create_table_definition(*args)
+ SQLite3::TableDefinition.new(*args)
+ end
+
+ def new_column_from_field(table_name, field)
+ default = \
+ case field["dflt_value"]
+ when /^null$/i
+ nil
+ when /^'(.*)'$/m
+ $1.gsub("''", "'")
+ when /^"(.*)"$/m
+ $1.gsub('""', '"')
+ else
+ field["dflt_value"]
+ end
+
+ type_metadata = fetch_type_metadata(field["type"])
+ Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
+ end
+
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
scope[:type] ||= "'table','view'"
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d24bfc0c93..c54b88f7d1 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -84,14 +84,6 @@ module ActiveRecord
SQLite3::Table.new(table_name, base)
end
- def schema_creation # :nodoc:
- SQLite3::SchemaCreation.new self
- end
-
- def arel_visitor # :nodoc:
- Arel::Visitors::SQLite.new(self)
- end
-
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@@ -267,22 +259,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def new_column_from_field(table_name, field) # :nondoc:
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/m
- field["dflt_value"] = $1.gsub("''", "'")
- when /^"(.*)"$/m
- field["dflt_value"] = $1.gsub('""', '"')
- end
-
- collation = field["collation"]
- sql_type = field["type"]
- type_metadata = fetch_type_metadata(sql_type)
- new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
- end
-
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
if name
@@ -559,16 +535,8 @@ module ActiveRecord
end
end
- def create_table_definition(*args)
- SQLite3::TableDefinition.new(*args)
- end
-
- def extract_foreign_key_action(specifier)
- case specifier
- when "CASCADE"; :cascade
- when "SET NULL"; :nullify
- when "RESTRICT"; :restrict
- end
+ def arel_visitor
+ Arel::Visitors::SQLite.new(self)
end
def configure_connection
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 08d42f3dd4..d9912cfb22 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module DynamicMatchers #:nodoc:
def respond_to_missing?(name, include_private = false)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e79167d568..c19216702c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -70,13 +70,32 @@ module ActiveRecord
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
- # using a special dynamic method, which has the same name as the model, and accepts the
- # name of the fixture to instantiate:
+ # using a special dynamic method, which has the same name as the model.
#
- # test "find" do
+ # Passing in a fixture name to this dynamic method returns the fixture matching this name:
+ #
+ # test "find one" do
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
# end
#
+ # Passing in multiple fixture names returns all fixtures matching these names:
+ #
+ # test "find all by name" do
+ # assert_equal 2, web_sites(:rubyonrails, :google).length
+ # end
+ #
+ # Passing in no arguments returns all fixtures:
+ #
+ # test "find all" do
+ # assert_equal 2, web_sites.length
+ # end
+ #
+ # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
+ #
+ # test "find by name that does not exist" do
+ # assert_raise(StandardError) { web_sites(:reddit) }
+ # end
+ #
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
# following tests:
#
@@ -909,6 +928,8 @@ module ActiveRecord
define_method(accessor_name) do |*fixture_names|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
+ return_single_record = fixture_names.size == 1
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
@fixture_cache[fs_name] ||= {}
@@ -923,7 +944,7 @@ module ActiveRecord
end
end
- instances.size == 1 ? instances.first : instances
+ return_single_record ? instances.first : instances
end
private accessor_name
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 0276d41494..73518ca144 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -166,5 +166,13 @@ end_warning
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
+
+ initializer "active_record.clear_active_connections" do
+ config.after_initialize do
+ ActiveSupport.on_load(:active_record) do
+ clear_active_connections!
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 2d6b21bec5..5775eda5a5 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -635,7 +635,9 @@ module ActiveRecord
end
def inspect
- entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
+ subject = loaded? ? records : self
+ entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
+
entries[10] = "..." if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 5d24f5f5ca..a1459c87c6 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -312,16 +312,7 @@ module ActiveRecord
relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
- relation = relation.except(:select, :distinct).select(ONE_AS_ONE).limit(1)
-
- case conditions
- when Array, Hash
- relation = relation.where(conditions)
- else
- unless conditions == :none
- relation = relation.where(primary_key => conditions)
- end
- end
+ relation = construct_relation_for_exists(relation, conditions)
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
rescue ::RangeError
@@ -391,6 +382,19 @@ module ActiveRecord
end
end
+ def construct_relation_for_exists(relation, conditions)
+ relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+
+ case conditions
+ when Array, Hash
+ relation.where!(conditions)
+ else
+ relation.where!(primary_key => conditions) unless conditions == :none
+ end
+
+ relation
+ end
+
def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
@@ -401,8 +405,7 @@ module ActiveRecord
end
def apply_join_dependency(relation, join_dependency)
- relation = relation.except(:includes, :eager_load, :preload)
- relation = relation.joins join_dependency
+ relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency)
if using_limitable_reflections?(join_dependency.reflections)
relation
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2bbfd01698..94d63765c9 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -47,9 +47,18 @@ module ActiveRecord
@options = options
end
- def header(stream)
- define_params = @version ? "version: #{@version}" : ""
+ # turns 20170404131909 into "2017_04_04_131909"
+ def formatted_version
+ stringified = @version.to_s
+ return stringified unless stringified.length == 14
+ stringified.insert(4, "_").insert(7, "_").insert(10, "_")
+ end
+ def define_params
+ @version ? "version: #{formatted_version}" : ""
+ end
+
+ def header(stream)
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 2fa39282fb..58698d59db 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -38,7 +38,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal :string, string_column.type
end
- test "test type casting with emulated booleans" do
+ test "type casting with emulated booleans" do
emulate_booleans true
boolean = BooleanType.create!(archived: true, published: true)
@@ -55,7 +55,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal 0, @connection.type_cast(false)
end
- test "test type casting without emulated booleans" do
+ test "type casting without emulated booleans" do
emulate_booleans false
boolean = BooleanType.create!(archived: true, published: true)
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 7b065ff320..75e30e4fe9 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -91,6 +91,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
+ @connection.execute "CREATE TABLE #{SCHEMA2_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
@@ -361,7 +362,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
def test_primary_key_assuming_schema_search_path
- with_schema_search_path(SCHEMA_NAME) do
+ with_schema_search_path("#{SCHEMA_NAME}, #{SCHEMA2_NAME}") do
assert_equal "id", @connection.primary_key(PK_TABLE_NAME), "primary key should be found"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6aa6a79705..52e4a38cae 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -13,6 +13,10 @@ module PostgresqlUUIDHelper
def uuid_function
connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
end
+
+ def uuid_default
+ connection.supports_pgcrypto_uuid? ? {} : { default: uuid_function }
+ end
end
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -178,7 +182,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
t.uuid "other_uuid_2", default: "my_uuid_generator()"
end
- connection.create_table("pg_uuids_3", id: :uuid) do |t|
+ connection.create_table("pg_uuids_3", id: :uuid, **uuid_default) do |t|
t.string "name"
end
end
@@ -320,10 +324,10 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
setup do
connection.transaction do
- connection.create_table("pg_uuid_posts", id: :uuid) do |t|
+ connection.create_table("pg_uuid_posts", id: :uuid, **uuid_default) do |t|
t.string "title"
end
- connection.create_table("pg_uuid_comments", id: :uuid) do |t|
+ connection.create_table("pg_uuid_comments", id: :uuid, **uuid_default) do |t|
t.references :uuid_post, type: :uuid
t.string "content"
end
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index bb2829b3c1..2c6a38ec35 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -15,7 +15,7 @@ module ActiveRecord
@connection.drop_table :cache_mes, if_exists: true
end
- test "test_cache_key_format_is_not_too_precise" do
+ test "cache_key format is not too precise" do
record = CacheMe.create
key = record.cache_key
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index 59ef389326..a26a72712d 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index d230700119..90c8d21c43 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
def @adapter.native_database_types
{ string: "varchar" }
end
- @viz = @adapter.schema_creation
+ @viz = @adapter.send(:schema_creation)
end
# Avoid column definitions in create table statements like:
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 63f67a9a16..c23be52a6c 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -72,9 +72,11 @@ if ActiveRecord::Base.connection.supports_comments?
end
def test_add_index_with_comment_later
- @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
- index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
- assert_equal "We need to see obvious comments", index.comment
+ unless current_adapter?(:OracleAdapter)
+ @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
+ index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
+ assert_equal "We need to see obvious comments", index.comment
+ end
end
def test_add_comment_to_column
@@ -112,8 +114,10 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"obvious"\n], output
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
- assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
- assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ unless current_adapter?(:OracleAdapter)
+ assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
+ end
end
def test_schema_dump_omits_blank_comments
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 3821e0c949..000ed27efb 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -143,6 +143,8 @@ module ActiveRecord
end
def test_dup_without_primary_key
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "parrots_pirates"
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 89d8a8bdca..a7b6333010 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -202,11 +202,29 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
- # ensures +exists?+ runs valid SQL by excluding order value
- def test_exists_with_order
+ # Ensure +exists?+ runs without an error by excluding distinct value.
+ # See https://github.com/rails/rails/pull/26981.
+ def test_exists_with_order_and_distinct
assert_equal true, Topic.order(:id).distinct.exists?
end
+ # Ensure +exists?+ runs without an error by excluding order value.
+ def test_exists_with_order
+ assert_equal true, Topic.order("invalid sql here").exists?
+ end
+
+ def test_exists_with_joins
+ assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_left_joins
+ assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_eager_load
+ assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
def test_exists_with_includes_limit_and_empty_result
assert_equal false, Topic.includes(:replies).limit(0).exists?
assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists?
@@ -236,9 +254,9 @@ class FinderTest < ActiveRecord::TestCase
def test_exists_with_aggregate_having_three_mappings_with_one_difference
existing_address = customers(:david).address
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
def test_exists_does_not_instantiate_records
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 3720b0cc1a..a0a6d3c7ef 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -95,6 +95,24 @@ class FixturesTest < ActiveRecord::TestCase
assert_nil(topics["second"]["author_email_address"])
end
+ def test_no_args_returns_all
+ all_topics = topics
+ assert_equal 5, all_topics.length
+ assert_equal "The First Topic", all_topics.first["title"]
+ assert_equal 5, all_topics.last.id
+ end
+
+ def test_no_args_record_returns_all_without_array
+ all_binaries = binaries
+ assert_kind_of(Array, all_binaries)
+ assert_equal 1, binaries.length
+ end
+
+ def test_nil_raises
+ assert_raise(StandardError) { topics(nil) }
+ assert_raise(StandardError) { topics([nil]) }
+ end
+
def test_inserts
create_fixtures("topics")
first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index d7aa091623..0678bb714f 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
require "models/company"
require "models/developer"
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 19588d28a2..7bcabd0cc6 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -80,7 +80,7 @@ module ActiveRecord
end
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- connection.create_table :cats, id: :uuid
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
ensure
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 12386635f6..5ded619716 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -183,6 +183,8 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_create_without_primary_key_no_extra_query
+ skip if current_adapter?(:OracleAdapter)
+
klass = Class.new(ActiveRecord::Base) do
self.table_name = "dashboards"
end
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4f92f71a09..11ef0d8743 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -143,7 +143,7 @@ module ActiveRecord
assert_equal({ foo: "bar" }, relation.create_with_value)
end
- test "test_merge!" do
+ test "merge!" do
assert relation.merge!(select: :foo).equal?(relation)
assert_equal [:foo], relation.select_values
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index c7b2ac90fb..cbc466d6b8 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -289,6 +289,11 @@ module ActiveRecord
assert_equal essays(:david_modest_proposal), essay
end
+ def test_where_on_association_with_select_relation
+ essay = Essay.where(author: Author.where(name: "David").select(:name)).take
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
def test_where_with_strong_parameters
protected_params = Class.new do
attr_reader :permitted
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8c06b1537a..fcf68b0f2a 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1901,6 +1901,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect
end
+ test "relations don't load all records in #inspect" do
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
+ Post.all.inspect
+ end
+ end
+
test "already-loaded relations don't perform a new query in #inspect" do
relation = Post.limit(2)
relation.to_a
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index a6c22ac672..89fb434b27 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -462,6 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_with_abstract_class_scope_should_be_executed_in_correct_context
vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter)
[/`lions`.`is_vegetarian`/, /`lions`.`gender`/]
+ elsif current_adapter?(:OracleAdapter)
+ [/"LIONS"."IS_VEGETARIAN"/, /"LIONS"."GENDER"/]
else
[/"lions"."is_vegetarian"/, /"lions"."gender"/]
end
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 07288568e8..1d21a2454f 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -154,7 +154,9 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ if current_adapter?(:Mysql2Adapter, :SQLServerAdapter) ||
+ current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.postgresql_version >= 90300
+
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb
index 13267fbc21..1f9772870e 100644
--- a/activerecord/test/models/essay.rb
+++ b/activerecord/test/models/essay.rb
@@ -1,4 +1,5 @@
class Essay < ActiveRecord::Base
+ belongs_to :author
belongs_to :writer, primary_key: :name, polymorphic: true
belongs_to :category, primary_key: :name
has_one :owner, primary_key: :name
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 860c63b27c..e56e8fa36a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -3,11 +3,13 @@ ActiveRecord::Schema.define do
enable_extension!("uuid-ossp", ActiveRecord::Base.connection)
enable_extension!("pgcrypto", ActiveRecord::Base.connection) if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
- create_table :uuid_parents, id: :uuid, force: true do |t|
+ uuid_default = connection.supports_pgcrypto_uuid? ? {} : { default: "uuid_generate_v4()" }
+
+ create_table :uuid_parents, id: :uuid, force: true, **uuid_default do |t|
t.string :name
end
- create_table :uuid_children, id: :uuid, force: true do |t|
+ create_table :uuid_children, id: :uuid, force: true, **uuid_default do |t|
t.string :name
t.uuid :uuid_parent_id
end
@@ -102,7 +104,7 @@ _SQL
end
create_table :uuid_items, force: true, id: false do |t|
- t.uuid :uuid, primary_key: true
+ t.uuid :uuid, primary_key: true, **uuid_default
t.string :title
end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 6f99155199..1adc473e1d 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1 +1,7 @@
+* Add `fetch_values` for `HashWithIndifferentAccess`
+
+ The method was originally added to `Hash` in Ruby 2.3.0.
+
+ *Josh Pencheon*
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index efb9d1b8a0..061c959442 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -12,6 +12,7 @@ class Hash
def reverse_merge(other_hash)
other_hash.merge(self)
end
+ alias_method :with_defaults, :reverse_merge
# Destructive +reverse_merge+.
def reverse_merge!(other_hash)
@@ -19,4 +20,5 @@ class Hash
merge!(other_hash) { |key, left, right| left }
end
alias_method :reverse_update, :reverse_merge!
+ alias_method :with_defaults!, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index cdf27f49ad..85ab739095 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -267,7 +267,10 @@ class Module
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
- #{target}.respond_to?(name, include_private)
+ # It may look like an oversight, but we deliberately do not pass
+ # +include_private+, because they do not get delegated.
+
+ #{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 7a2fc5c1b5..b27cfe53f4 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -100,12 +100,17 @@ class String
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- def titleize
- ActiveSupport::Inflector.titleize(self)
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
+ # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(keep_id_suffix: false)
+ ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
end
alias_method :titlecase, :titleize
@@ -202,7 +207,7 @@ class String
ActiveSupport::Inflector.classify(self)
end
- # Capitalizes the first word, turns underscores into spaces, and strips a
+ # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
# trailing '_id' if present.
# Like +titleize+, this is meant for creating pretty output.
#
@@ -210,12 +215,17 @@ class String
# optional parameter +capitalize+ to false.
# By default, this parameter is true.
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- # 'author_id'.humanize(capitalize: false) # => "author"
- # '_id'.humanize # => "Id"
- def humanize(options = {})
- ActiveSupport::Inflector.humanize(self, options)
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
+ # 'author_id'.humanize(capitalize: false) # => "author"
+ # '_id'.humanize # => "Id"
+ # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id"
+ def humanize(capitalize: true, keep_id_suffix: false)
+ ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
end
# Converts just the first character to uppercase.
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 8e0dc71dca..f59f5d17dc 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -125,6 +125,11 @@ module ActiveSupport
dtw.compact!
dtw.uniq!
+ normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
+ dtw = dtw.reject do |path|
+ normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
+ end
+
@ph.filter_out_descendants(dtw)
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 1927cddf34..3b185dd86b 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -195,6 +195,19 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
+ # Returns an array of the values at the specified indices, but also
+ # raises an exception when one of the keys can't be found.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:a] = 'x'
+ # hash[:b] = 'y'
+ # hash.fetch_values('a', 'b') # => ["x", "y"]
+ # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
+ # hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
+ def fetch_values(*indices, &block)
+ indices.collect { |key| fetch(key, &block) }
+ end if Hash.method_defined?(:fetch_values)
+
# Returns a shallow copy of the hash.
#
# hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
@@ -225,11 +238,13 @@ module ActiveSupport
def reverse_merge(other_hash)
super(self.class.new(other_hash))
end
+ alias_method :with_defaults, :reverse_merge
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
replace(reverse_merge(other_hash))
end
+ alias_method :with_defaults!, :reverse_merge!
# Replaces the contents of this hash with other_hash.
#
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 51c221ac0e..1b089a7538 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -108,33 +108,38 @@ module ActiveSupport
# * Replaces underscores with spaces, if any.
# * Downcases all words except acronyms.
# * Capitalizes the first word.
- #
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
#
- # humanize('employee_salary') # => "Employee salary"
- # humanize('author_id') # => "Author"
- # humanize('author_id', capitalize: false) # => "author"
- # humanize('_id') # => "Id"
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true (default is false).
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
+ # humanize('author_id', keep_id_suffix: true) # => "Author Id"
#
# If "SSL" was defined to be an acronym:
#
# humanize('ssl_error') # => "SSL error"
#
- def humanize(lower_case_and_underscored_word, options = {})
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
result = lower_case_and_underscored_word.to_s.dup
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.sub!(/\A_+/, "".freeze)
- result.sub!(/_id\z/, "".freeze)
+ unless keep_id_suffix
+ result.sub!(/_id\z/, "".freeze)
+ end
result.tr!("_".freeze, " ".freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
end
- if options.fetch(:capitalize, true)
+ if capitalize
result.sub!(/\A\w/) { |match| match.upcase }
end
@@ -154,14 +159,21 @@ module ActiveSupport
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # titleize('man from the boondocks') # => "Man From The Boondocks"
- # titleize('x-men: the last stand') # => "X Men: The Last Stand"
- # titleize('TheManWithoutAPast') # => "The Man Without A Past"
- # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
- def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!\w['’`])[a-z]/) { |match| match.capitalize }
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(word, keep_id_suffix: false)
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match|
+ match.capitalize
+ end
end
# Creates the name of a table like Rails does for models to table names.
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 18477b9f6b..ce5207546d 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -250,14 +250,21 @@ module ActiveSupport
# for time zones in the country specified by its ISO 3166-1 Alpha2 code.
def country_zones(country_code)
code = country_code.to_s.upcase
- @country_zones[code] ||=
- TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
- name = MAPPING.key(tz_id)
- name && self[name]
- end.compact.sort!
+ @country_zones[code] ||= load_country_zones(code)
end
private
+ def load_country_zones(code)
+ country = TZInfo::Country.get(code)
+ country.zone_identifiers.map do |tz_id|
+ if MAPPING.value?(tz_id)
+ self[MAPPING.key(tz_id)]
+ else
+ create(tz_id, nil, TZInfo::Timezone.new(tz_id))
+ end
+ end.sort!
+ end
+
def zones_map
@zones_map ||= begin
MAPPING.each_key { |place| self[place] } # load all the zones
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 525ea08abd..18da5fcf5f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -8,33 +8,6 @@ require "active_support/core_ext/object/deep_dup"
require "active_support/inflections"
class HashExtTest < ActiveSupport::TestCase
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
- class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
- end
-
- class SubclassingArray < Array
- end
-
- class SubclassingHash < Hash
- end
-
- class NonIndifferentHash < Hash
- def nested_under_indifferent_access
- self
- end
- end
-
- class HashByConversion
- def initialize(hash)
- @hash = hash
- end
-
- def to_hash
- @hash
- end
- end
-
def setup
@strings = { "a" => 1, "b" => 2 }
@nested_strings = { "a" => { "b" => { "c" => 3 } } }
@@ -264,471 +237,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
- def test_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
- end
-
- def test_deep_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
- end
-
- def test_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @strings.with_indifferent_access.stringify_keys
- assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
- end
-
- def test_deep_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
- end
-
- def test_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
- end
-
- def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
- end
-
- def test_nested_under_indifferent_access
- foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
-
- foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of NonIndifferentHash, foo["foo"]
-
- foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of IndifferentHash, foo["foo"]
- end
-
- def test_indifferent_assorted
- @strings = @strings.with_indifferent_access
- @symbols = @symbols.with_indifferent_access
- @mixed = @mixed.with_indifferent_access
-
- assert_equal "a", @strings.__send__(:convert_key, :a)
-
- assert_equal 1, @strings.fetch("a")
- assert_equal 1, @strings.fetch(:a.to_s)
- assert_equal 1, @strings.fetch(:a)
-
- hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
- method_map = { '[]': 1, fetch: 1, values_at: [1],
- has_key?: true, include?: true, key?: true,
- member?: true }
-
- hashes.each do |name, hash|
- method_map.sort_by(&:to_s).each do |meth, expected|
- assert_equal(expected, hash.__send__(meth, "a"),
- "Calling #{name}.#{meth} 'a'")
- assert_equal(expected, hash.__send__(meth, :a),
- "Calling #{name}.#{meth} :a")
- end
- end
-
- assert_equal [1, 2], @strings.values_at("a", "b")
- assert_equal [1, 2], @strings.values_at(:a, :b)
- assert_equal [1, 2], @symbols.values_at("a", "b")
- assert_equal [1, 2], @symbols.values_at(:a, :b)
- assert_equal [1, 2], @mixed.values_at("a", "b")
- assert_equal [1, 2], @mixed.values_at(:a, :b)
- end
-
- def test_indifferent_reading
- hash = HashWithIndifferentAccess.new
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_nil hash[:e]
- end
-
- def test_indifferent_reading_with_nonnil_default
- hash = HashWithIndifferentAccess.new(1)
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_equal 1, hash[:e]
- end
-
- def test_indifferent_writing
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 2
- hash[3] = 3
-
- assert_equal 1, hash["a"]
- assert_equal 2, hash["b"]
- assert_equal 1, hash[:a]
- assert_equal 2, hash[:b]
- assert_equal 3, hash[3]
- end
-
- def test_indifferent_update
- hash = HashWithIndifferentAccess.new
- hash[:a] = "a"
- hash["b"] = "b"
-
- updated_with_strings = hash.update(@strings)
- updated_with_symbols = hash.update(@symbols)
- updated_with_mixed = hash.update(@mixed)
-
- assert_equal 1, updated_with_strings[:a]
- assert_equal 1, updated_with_strings["a"]
- assert_equal 2, updated_with_strings["b"]
-
- assert_equal 1, updated_with_symbols[:a]
- assert_equal 2, updated_with_symbols["b"]
- assert_equal 2, updated_with_symbols[:b]
-
- assert_equal 1, updated_with_mixed[:a]
- assert_equal 2, updated_with_mixed["b"]
-
- assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
- end
-
- def test_update_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash.update HashByConversion.new(a: 1)
- assert_equal 1, hash["a"]
- end
-
- def test_indifferent_merging
- hash = HashWithIndifferentAccess.new
- hash[:a] = "failure"
- hash["b"] = "failure"
-
- other = { "a" => 1, :b => 2 }
-
- merged = hash.merge(other)
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 1, merged[:a]
- assert_equal 2, merged["b"]
-
- hash.update(other)
-
- assert_equal 1, hash[:a]
- assert_equal 2, hash["b"]
- end
-
- def test_merge_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- merged = hash.merge HashByConversion.new(a: 1)
- assert_equal 1, merged["a"]
- end
-
- def test_indifferent_replace
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(b: 12)
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_replace_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(HashByConversion.new(b: 12))
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_indifferent_merging_with_block
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 3
-
- other = { "a" => 4, :b => 2, "c" => 10 }
-
- merged = hash.merge(other) { |key, old, new| old > new ? old : new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 4, merged[:a]
- assert_equal 3, merged["b"]
- assert_equal 10, merged[:c]
-
- other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
-
- merged = hash.merge(other_indifferent) { |key, old, new| old + new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 10, merged[:a]
- assert_equal 5, merged[:b]
- end
-
- def test_indifferent_reverse_merging
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
-
- hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
- hash.reverse_merge!(some: "noclobber", another: "clobber")
- assert_equal "value", hash[:some]
- assert_equal "clobber", hash[:another]
- end
-
- def test_indifferent_deleting
- get_hash = proc { { a: "foo" }.with_indifferent_access }
- hash = get_hash.call
- assert_equal "foo", hash.delete(:a)
- assert_nil hash.delete(:a)
- hash = get_hash.call
- assert_equal "foo", hash.delete("a")
- assert_nil hash.delete("a")
- end
-
- def test_indifferent_select
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_select_returns_a_hash_when_unchanged
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
-
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.select! { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_reject
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_reject_returns_enumerator
- enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
- assert_instance_of Enumerator, enum
- end
-
- def test_indifferent_reject_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.reject! { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_compact
- hash_contain_nil_value = @strings.merge("z" => nil)
- hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
- compacted_hash = hash.compact
-
- assert_equal(@strings, compacted_hash)
- assert_equal(hash_contain_nil_value, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
-
- empty_hash = ActiveSupport::HashWithIndifferentAccess.new
- compacted_hash = empty_hash.compact
-
- assert_equal compacted_hash, empty_hash
-
- non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
- compacted_hash = non_empty_hash.compact
-
- assert_equal compacted_hash, non_empty_hash
- end
-
- def test_indifferent_to_hash
- # Should convert to a Hash with String keys.
- assert_equal @strings, @mixed.with_indifferent_access.to_hash
-
- # Should preserve the default value.
- mixed_with_default = @mixed.dup
- mixed_with_default.default = "1234"
- roundtrip = mixed_with_default.with_indifferent_access.to_hash
- assert_equal @strings, roundtrip
- assert_equal "1234", roundtrip.default
-
- # Ensure nested hashes are not HashWithIndiffereneAccess
- new_to_hash = @nested_mixed.with_indifferent_access.to_hash
- assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
- end
-
- def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
- hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
- hash[:a] << 1
-
- assert_equal [1], hash[:a]
- end
-
- def test_with_indifferent_access_has_no_side_effects_on_existing_hash
- hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
- hash.with_indifferent_access
-
- assert_equal [:foo, "bar"], hash[:content].first.keys
- end
-
- def test_indifferent_hash_with_array_of_hashes
- hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
- assert_equal "1", hash[:urls][:url].first[:address]
-
- hash = hash.to_hash
- assert_not hash.instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
- end
-
- def test_should_preserve_array_subclass_when_value_is_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_should_preserve_array_class_when_hash_value_is_frozen_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.deep_stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.deep_symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_to_options_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h.to_options!
- assert_equal 1, h["first"]
- end
-
- def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
- h = HashWithIndifferentAccess.new(a: { b: "b" })
- dup = h.dup
-
- dup[:a][:c] = "c"
- assert_equal "c", h[:a][:c]
- end
-
- def test_indifferent_sub_hashes
- h = { "user" => { "id" => 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
-
- h = { user: { id: 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
- end
-
- def test_indifferent_duplication
- # Should preserve default value
- h = HashWithIndifferentAccess.new
- h.default = "1234"
- assert_equal h.default, h.dup.default
-
- # Should preserve class for subclasses
- h = IndifferentHash.new
- assert_equal h.class, h.dup.class
- end
-
- def test_nested_dig_indifferent_access
- skip if RUBY_VERSION < "2.3.0"
- data = { "this" => { "views" => 1234 } }.with_indifferent_access
- assert_equal 1234, data.dig(:this, :views)
- end
-
def test_assert_valid_keys
assert_nothing_raised do
{ failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ])
@@ -761,12 +269,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
end
- def test_assorted_keys_not_stringified
- original = { Object.new => 2, 1 => 2, [] => true }
- indiff = original.with_indifferent_access
- assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
- end
-
def test_deep_merge
hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } }
hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
@@ -797,37 +299,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
- def test_deep_merge_on_indifferent_access
- hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
- hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
- hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
- expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
- assert_equal expected, hash_1.deep_merge(hash_2)
- assert_equal expected, hash_1.deep_merge(hash_3)
-
- hash_1.deep_merge!(hash_2)
- assert_equal expected, hash_1
- end
-
- def test_store_on_indifferent_access
- hash = HashWithIndifferentAccess.new
- hash.store(:test1, 1)
- hash.store("test1", 11)
- hash[:test2] = 2
- hash["test2"] = 22
- expected = { "test1" => 11, "test2" => 22 }
- assert_equal expected, hash
- end
-
- def test_constructor_on_indifferent_access
- hash = HashWithIndifferentAccess[:foo, 1]
- assert_equal 1, hash[:foo]
- assert_equal 1, hash["foo"]
- hash[:foo] = 3
- assert_equal 3, hash[:foo]
- assert_equal 3, hash["foo"]
- end
-
def test_reverse_merge
defaults = { a: "x", b: "y", c: 10 }.freeze
options = { a: 1, b: 2 }
@@ -848,6 +319,21 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, merged
end
+ def test_with_defaults_aliases_reverse_merge
+ defaults = { a: "x", b: "y", c: 10 }.freeze
+ options = { a: 1, b: 2 }
+ expected = { a: 1, b: 2, c: 10 }
+
+ # Should be an alias for reverse_merge
+ assert_equal expected, options.with_defaults(defaults)
+ assert_not_equal expected, options
+
+ # Should be an alias for reverse_merge!
+ merged = options.dup
+ assert_equal expected, merged.with_defaults!(defaults)
+ assert_equal expected, merged
+ end
+
def test_slice
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -890,38 +376,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, original.slice(*[:a, :b])
end
- def test_indifferent_slice
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { a: "x", b: "y" }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should return a new hash with only the given keys.
- assert_equal expected, original.slice(*keys), keys.inspect
- assert_not_equal expected, original
- end
- end
-
- def test_indifferent_slice_inplace
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { c: 10 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should replace the hash with only the given keys.
- copy = original.dup
- assert_equal expected, copy.slice!(*keys)
- end
- end
-
- def test_indifferent_slice_access_with_symbols
- original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
- original = original.with_indifferent_access
-
- slice = original.slice(:login, :password)
-
- assert_equal "bender", slice[:login]
- assert_equal "bender", slice["login"]
- end
-
def test_slice_bang_does_not_override_default
hash = Hash.new(0)
hash.update(a: 1, b: 2)
@@ -959,18 +413,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil extracted[:x]
end
- def test_indifferent_extract
- original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
- expected = { a: 1, b: 2 }.with_indifferent_access
- remaining = { c: 3, d: 4 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- copy = original.dup
- assert_equal expected, copy.extract!(*keys)
- assert_equal remaining, copy
- end
- end
-
def test_except
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -1042,78 +484,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil(h.compact!)
assert_equal(@symbols, h)
end
-
- def test_new_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
- assert hash.key?("a")
- assert_equal 1, hash[:a]
- end
-
- def test_dup_with_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, v| raise "walrus" }
- assert_nothing_raised { hash.dup }
- end
-
- def test_dup_with_default_proc_sets_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| k + 1 }
- new_hash = hash.dup
-
- assert_equal 3, new_hash[2]
-
- new_hash.default = 2
- assert_equal 2, new_hash[:non_existent]
- end
-
- def test_to_hash_with_raising_default_proc
- hash = HashWithIndifferentAccess.new
- hash.default_proc = proc { |h, k| raise "walrus" }
-
- assert_nothing_raised { hash.to_hash }
- end
-
- def test_new_with_to_hash_conversion_copies_default
- normal_hash = Hash.new(3)
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
-
- def test_new_with_to_hash_conversion_copies_default_proc
- normal_hash = Hash.new { 1 + 2 }
- normal_hash[:a] = 1
-
- hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
- assert_equal 1, hash[:a]
- assert_equal 3, hash[:b]
- end
-
- def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
- klass = Class.new(::HashWithIndifferentAccess)
- assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
- end
-
- def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
- klass = Class.new(::HashWithIndifferentAccess) do
- def initialize(*)
- @foo = "bar"
- super
- end
- end
-
- yaml_output = klass.new.to_yaml
-
- # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
- if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
- assert_includes yaml_output, "hash-with-ivars"
- assert_includes yaml_output, "@foo: bar"
- else
- assert_includes yaml_output, "hash"
- end
- end
end
class IWriteMyOwnXML
@@ -1159,8 +529,6 @@ class HashExtToParamTests < ActiveSupport::TestCase
end
class HashToXmlTest < ActiveSupport::TestCase
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
-
def setup
@xml_options = { root: :person, skip_instruct: true, indent: 0 }
end
@@ -1636,61 +1004,6 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
- def test_should_use_default_proc_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_return_nil_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_nil hash_wia.default
- end
-
- def test_should_use_default_value_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_use_default_value_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia.default
- end
-
- def test_should_nil_if_no_default_value_is_supplied
- hash_wia = HashWithIndifferentAccess.new
- assert_nil hash_wia.default
- end
-
- def test_should_return_dup_for_with_indifferent_access
- hash_wia = HashWithIndifferentAccess.new
- assert_equal hash_wia, hash_wia.with_indifferent_access
- assert_not_same hash_wia, hash_wia.with_indifferent_access
- end
-
- def test_allows_setting_frozen_array_values_with_indifferent_access
- value = [1, 2, 3].freeze
- hash = HashWithIndifferentAccess.new
- hash[:key] = value
- assert_equal hash[:key], value
- end
-
- def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
- hash = Hash.new(3)
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia.default
- end
-
- def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
- hash = Hash.new do
- 2 + 1
- end
- assert_equal 3, hash[:foo]
-
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia[:foo]
- assert_equal 3, hash_wia[:bar]
- end
-
# The XML builder seems to fail miserably when trying to tag something
# with the same name as a Kernel method (throw, test, loop, select ...)
def test_kernel_method_names_to_xml
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a17438bf4d..085fd6592d 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -70,7 +70,23 @@ Product = Struct.new(:name) do
end
end
+module ExtraMissing
+ def method_missing(sym, *args)
+ if sym == :extra_missing
+ 42
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(sym, priv = false)
+ sym == :extra_missing || super
+ end
+end
+
DecoratedTester = Struct.new(:client) do
+ include ExtraMissing
+
delegate_missing_to :client
end
@@ -356,6 +372,22 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `my_fake_method' for/, e.message)
end
+ def test_delegate_to_missing_affects_respond_to
+ assert DecoratedTester.new(@david).respond_to?(:name)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method)
+
+ assert DecoratedTester.new(@david).respond_to?(:name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true)
+ end
+
+ def test_delegate_to_missing_respects_superclass_missing
+ assert_equal 42, DecoratedTester.new(@david).extra_missing
+
+ assert_respond_to DecoratedTester.new(@david), :extra_missing
+ end
+
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 41cc9888c6..a98951e889 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -78,6 +78,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_titleize_with_keep_id_suffix
+ MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized|
+ assert_equal(titleized, before.titleize(keep_id_suffix: true))
+ end
+ end
+
def test_upcase_first
assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
end
@@ -199,6 +205,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, underscore.humanize(keep_id_suffix: true))
+ end
+ end
+
def test_humanize_with_html_escape
assert_equal "Hello", ERB::Util.html_escape("hello").humanize
end
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
new file mode 100644
index 0000000000..d68add46cd
--- /dev/null
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -0,0 +1,745 @@
+require "abstract_unit"
+require "active_support/core_ext/hash"
+require "bigdecimal"
+require "active_support/core_ext/string/access"
+require "active_support/ordered_hash"
+require "active_support/core_ext/object/conversions"
+require "active_support/core_ext/object/deep_dup"
+require "active_support/inflections"
+
+class HashWithIndifferentAccessTest < ActiveSupport::TestCase
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
+
+ class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
+ end
+
+ class SubclassingArray < Array
+ end
+
+ class SubclassingHash < Hash
+ end
+
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
+ class HashByConversion
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_hash
+ @hash
+ end
+ end
+
+ def setup
+ @strings = { "a" => 1, "b" => 2 }
+ @nested_strings = { "a" => { "b" => { "c" => 3 } } }
+ @symbols = { a: 1, b: 2 }
+ @nested_symbols = { a: { b: { c: 3 } } }
+ @mixed = { :a => 1, "b" => 2 }
+ @nested_mixed = { "a" => { b: { "c" => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3 } } }
+ @illegal_symbols = { [] => 3 }
+ @nested_illegal_symbols = { [] => { [] => 3 } }
+ end
+
+ def test_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
+ end
+
+ def test_deep_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
+ end
+
+ def test_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @strings.with_indifferent_access.stringify_keys
+ assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
+ end
+
+ def test_deep_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
+ end
+
+ def test_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
+ end
+
+ def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
+ end
+
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
+
+ foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of IndifferentHash, foo["foo"]
+ end
+
+ def test_indifferent_assorted
+ @strings = @strings.with_indifferent_access
+ @symbols = @symbols.with_indifferent_access
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal "a", @strings.__send__(:convert_key, :a)
+
+ assert_equal 1, @strings.fetch("a")
+ assert_equal 1, @strings.fetch(:a.to_s)
+ assert_equal 1, @strings.fetch(:a)
+
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
+ method_map = { '[]': 1, fetch: 1, values_at: [1],
+ has_key?: true, include?: true, key?: true,
+ member?: true }
+
+ hashes.each do |name, hash|
+ method_map.sort_by(&:to_s).each do |meth, expected|
+ assert_equal(expected, hash.__send__(meth, "a"),
+ "Calling #{name}.#{meth} 'a'")
+ assert_equal(expected, hash.__send__(meth, :a),
+ "Calling #{name}.#{meth} :a")
+ end
+ end
+
+ assert_equal [1, 2], @strings.values_at("a", "b")
+ assert_equal [1, 2], @strings.values_at(:a, :b)
+ assert_equal [1, 2], @symbols.values_at("a", "b")
+ assert_equal [1, 2], @symbols.values_at(:a, :b)
+ assert_equal [1, 2], @mixed.values_at("a", "b")
+ assert_equal [1, 2], @mixed.values_at(:a, :b)
+ end
+
+ def test_indifferent_fetch_values
+ skip unless Hash.method_defined?(:fetch_values)
+
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal [1, 2], @mixed.fetch_values("a", "b")
+ assert_equal [1, 2], @mixed.fetch_values(:a, :b)
+ assert_equal [1, 2], @mixed.fetch_values(:a, "b")
+ assert_equal [1, "c"], @mixed.fetch_values(:a, :c) { |key| key }
+ assert_raise(KeyError) { @mixed.fetch_values(:a, :c) }
+ end
+
+ def test_indifferent_reading
+ hash = HashWithIndifferentAccess.new
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_nil hash[:e]
+ end
+
+ def test_indifferent_reading_with_nonnil_default
+ hash = HashWithIndifferentAccess.new(1)
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_equal 1, hash[:e]
+ end
+
+ def test_indifferent_writing
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 2
+ hash[3] = 3
+
+ assert_equal 1, hash["a"]
+ assert_equal 2, hash["b"]
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash[:b]
+ assert_equal 3, hash[3]
+ end
+
+ def test_indifferent_update
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "a"
+ hash["b"] = "b"
+
+ updated_with_strings = hash.update(@strings)
+ updated_with_symbols = hash.update(@symbols)
+ updated_with_mixed = hash.update(@mixed)
+
+ assert_equal 1, updated_with_strings[:a]
+ assert_equal 1, updated_with_strings["a"]
+ assert_equal 2, updated_with_strings["b"]
+
+ assert_equal 1, updated_with_symbols[:a]
+ assert_equal 2, updated_with_symbols["b"]
+ assert_equal 2, updated_with_symbols[:b]
+
+ assert_equal 1, updated_with_mixed[:a]
+ assert_equal 2, updated_with_mixed["b"]
+
+ assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
+ end
+
+ def test_update_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash.update HashByConversion.new(a: 1)
+ assert_equal 1, hash["a"]
+ end
+
+ def test_indifferent_merging
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "failure"
+ hash["b"] = "failure"
+
+ other = { "a" => 1, :b => 2 }
+
+ merged = hash.merge(other)
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 1, merged[:a]
+ assert_equal 2, merged["b"]
+
+ hash.update(other)
+
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash["b"]
+ end
+
+ def test_merge_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ merged = hash.merge HashByConversion.new(a: 1)
+ assert_equal 1, merged["a"]
+ end
+
+ def test_indifferent_replace
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(b: 12)
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_replace_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(HashByConversion.new(b: 12))
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_indifferent_merging_with_block
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 3
+
+ other = { "a" => 4, :b => 2, "c" => 10 }
+
+ merged = hash.merge(other) { |key, old, new| old > new ? old : new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 4, merged[:a]
+ assert_equal 3, merged["b"]
+ assert_equal 10, merged[:c]
+
+ other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
+
+ merged = hash.merge(other_indifferent) { |key, old, new| old + new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 10, merged[:a]
+ assert_equal 5, merged[:b]
+ end
+
+ def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
+ hash = HashWithIndifferentAccess.new("some" => "value", "other" => "value")
+ hash.reverse_merge!(some: "noclobber", another: "clobber")
+ assert_equal "value", hash[:some]
+ assert_equal "clobber", hash[:another]
+ end
+
+ def test_indifferent_with_defaults_aliases_reverse_merge
+ hash = HashWithIndifferentAccess.new key: :old_value
+ actual = hash.with_defaults key: :new_value
+ assert_equal :old_value, actual[:key]
+
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.with_defaults! key: :new_value
+ assert_equal :old_value, hash[:key]
+ end
+
+ def test_indifferent_deleting
+ get_hash = proc { { a: "foo" }.with_indifferent_access }
+ hash = get_hash.call
+ assert_equal "foo", hash.delete(:a)
+ assert_nil hash.delete(:a)
+ hash = get_hash.call
+ assert_equal "foo", hash.delete("a")
+ assert_nil hash.delete("a")
+ end
+
+ def test_indifferent_select
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_select_returns_a_hash_when_unchanged
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
+
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.select! { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_reject
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
+ def test_indifferent_reject_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.reject! { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_compact
+ hash_contain_nil_value = @strings.merge("z" => nil)
+ hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
+ compacted_hash = hash.compact
+
+ assert_equal(@strings, compacted_hash)
+ assert_equal(hash_contain_nil_value, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
+
+ empty_hash = ActiveSupport::HashWithIndifferentAccess.new
+ compacted_hash = empty_hash.compact
+
+ assert_equal compacted_hash, empty_hash
+
+ non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
+ compacted_hash = non_empty_hash.compact
+
+ assert_equal compacted_hash, non_empty_hash
+ end
+
+ def test_indifferent_to_hash
+ # Should convert to a Hash with String keys.
+ assert_equal @strings, @mixed.with_indifferent_access.to_hash
+
+ # Should preserve the default value.
+ mixed_with_default = @mixed.dup
+ mixed_with_default.default = "1234"
+ roundtrip = mixed_with_default.with_indifferent_access.to_hash
+ assert_equal @strings, roundtrip
+ assert_equal "1234", roundtrip.default
+
+ # Ensure nested hashes are not HashWithIndiffereneAccess
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
+ def test_indifferent_hash_with_array_of_hashes
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
+ assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_should_preserve_array_subclass_when_value_is_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_should_preserve_array_class_when_hash_value_is_frozen_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.deep_stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.deep_symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_to_options_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h.to_options!
+ assert_equal 1, h["first"]
+ end
+
+ def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
+ h = HashWithIndifferentAccess.new(a: { b: "b" })
+ dup = h.dup
+
+ dup[:a][:c] = "c"
+ assert_equal "c", h[:a][:c]
+ end
+
+ def test_indifferent_sub_hashes
+ h = { "user" => { "id" => 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+
+ h = { user: { id: 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+ end
+
+ def test_indifferent_duplication
+ # Should preserve default value
+ h = HashWithIndifferentAccess.new
+ h.default = "1234"
+ assert_equal h.default, h.dup.default
+
+ # Should preserve class for subclasses
+ h = IndifferentHash.new
+ assert_equal h.class, h.dup.class
+ end
+
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = { "this" => { "views" => 1234 } }.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
+ def test_assorted_keys_not_stringified
+ original = { Object.new => 2, 1 => 2, [] => true }
+ indiff = original.with_indifferent_access
+ assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
+ end
+
+ def test_deep_merge_on_indifferent_access
+ hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
+ hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
+ hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
+ expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
+ assert_equal expected, hash_1.deep_merge(hash_2)
+ assert_equal expected, hash_1.deep_merge(hash_3)
+
+ hash_1.deep_merge!(hash_2)
+ assert_equal expected, hash_1
+ end
+
+ def test_store_on_indifferent_access
+ hash = HashWithIndifferentAccess.new
+ hash.store(:test1, 1)
+ hash.store("test1", 11)
+ hash[:test2] = 2
+ hash["test2"] = 22
+ expected = { "test1" => 11, "test2" => 22 }
+ assert_equal expected, hash
+ end
+
+ def test_constructor_on_indifferent_access
+ hash = HashWithIndifferentAccess[:foo, 1]
+ assert_equal 1, hash[:foo]
+ assert_equal 1, hash["foo"]
+ hash[:foo] = 3
+ assert_equal 3, hash[:foo]
+ assert_equal 3, hash["foo"]
+ end
+
+ def test_indifferent_slice
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { a: "x", b: "y" }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should return a new hash with only the given keys.
+ assert_equal expected, original.slice(*keys), keys.inspect
+ assert_not_equal expected, original
+ end
+ end
+
+ def test_indifferent_slice_inplace
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { c: 10 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should replace the hash with only the given keys.
+ copy = original.dup
+ assert_equal expected, copy.slice!(*keys)
+ end
+ end
+
+ def test_indifferent_slice_access_with_symbols
+ original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
+ original = original.with_indifferent_access
+
+ slice = original.slice(:login, :password)
+
+ assert_equal "bender", slice[:login]
+ assert_equal "bender", slice["login"]
+ end
+
+ def test_indifferent_extract
+ original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
+ expected = { a: 1, b: 2 }.with_indifferent_access
+ remaining = { c: 3, d: 4 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ copy = original.dup
+ assert_equal expected, copy.extract!(*keys)
+ assert_equal remaining, copy
+ end
+ end
+
+ def test_new_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
+ assert hash.key?("a")
+ assert_equal 1, hash[:a]
+ end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existent]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { hash.to_hash }
+ end
+
+ def test_new_with_to_hash_conversion_copies_default
+ normal_hash = Hash.new(3)
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_new_with_to_hash_conversion_copies_default_proc
+ normal_hash = Hash.new { 1 + 2 }
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
+ klass = Class.new(::HashWithIndifferentAccess)
+ assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
+ end
+
+ def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
+ klass = Class.new(::HashWithIndifferentAccess) do
+ def initialize(*)
+ @foo = "bar"
+ super
+ end
+ end
+
+ yaml_output = klass.new.to_yaml
+
+ # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
+ assert_includes yaml_output, "hash-with-ivars"
+ assert_includes yaml_output, "@foo: bar"
+ else
+ assert_includes yaml_output, "hash"
+ end
+ end
+
+ def test_should_use_default_proc_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_return_nil_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_nil hash_wia.default
+ end
+
+ def test_should_use_default_value_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_value_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_nil_if_no_default_value_is_supplied
+ hash_wia = HashWithIndifferentAccess.new
+ assert_nil hash_wia.default
+ end
+
+ def test_should_return_dup_for_with_indifferent_access
+ hash_wia = HashWithIndifferentAccess.new
+ assert_equal hash_wia, hash_wia.with_indifferent_access
+ assert_not_same hash_wia, hash_wia.with_indifferent_access
+ end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new(3)
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new do
+ 2 + 1
+ end
+ assert_equal 3, hash[:foo]
+
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia[:foo]
+ assert_equal 3, hash_wia[:bar]
+ end
+end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 03d7b3fe94..14bc10513b 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -119,6 +119,13 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index|
+ define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do
+ assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true),
+ "mixture to TitleCase with keep_id_suffix failed for #{before}")
+ end
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, ActiveSupport::Inflector.camelize(underscore))
@@ -324,6 +331,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true))
+ end
+ end
+
def test_humanize_by_rule
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index f3352e3301..d61ca3fc18 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -248,12 +248,27 @@ module InflectorTestCases
"_external_id" => "External"
}
+ UnderscoreToHumanWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This is a string ending with id",
+ "employee_id" => "Employee id",
+ "employee_id_something_else" => "Employee id something else",
+ "underground" => "Underground",
+ "_id" => "Id",
+ "_external_id" => "External id"
+ }
+
UnderscoreToHumanWithoutCapitalize = {
"employee_salary" => "employee salary",
"employee_id" => "employee",
"underground" => "underground"
}
+ MixtureToTitleCaseWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This Is A String Ending With Id",
+ "EmployeeId" => "Employee Id",
+ "Author Id" => "Author Id"
+ }
+
MixtureToTitleCase = {
"active_record" => "Active Record",
"ActiveRecord" => "Active Record",
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 1615d8fdb2..de111cc40e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -714,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"]
end
+ def test_country_zones_without_mappings
+ assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"]
+ end
+
def test_to_yaml
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index b56699a0d0..b27776745a 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -294,6 +294,10 @@ a, a:link, a:visited {
#mainCol a, #subCol a, #feature a {color: #980905;}
#mainCol a code, #subCol a code, #feature a code {color: #980905;}
+#mainCol a.anchorlink, #mainCol a.anchorlink code {color: #333;}
+#mainCol a.anchorlink { text-decoration: none; }
+#mainCol a.anchorlink:hover { text-decoration: underline; }
+
/* Navigation
--------------------------------------- */
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index bf2cc82c7c..16aaa7d1eb 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -105,6 +105,10 @@ module RailsGuides
node.inner_html = "#{node_index(hierarchy)} #{node.inner_html}"
end
end
+
+ doc.css('h3, h4, h5, h6').each do |node|
+ node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>"
+ end
end.to_html
end
end
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index 5d4885d55c..e995c50297 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -24,7 +24,14 @@ repository on GitHub.
Upgrading to Rails 5.1
----------------------
-ToDo
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 5.0 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 5.1. A list of things to watch out for when upgrading is
+available in the
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-5-0-to-rails-5-1)
+guide.
+
Major Features
--------------
@@ -223,41 +230,89 @@ Railties
Please refer to the [Changelog][railties] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Action Pack
-----------
Please refer to the [Changelog][action-pack] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Action View
-------------
Please refer to the [Changelog][action-view] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Action Mailer
-------------
Please refer to the [Changelog][action-mailer] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Active Record
-------------
Please refer to the [Changelog][active-record] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Active Model
------------
Please refer to the [Changelog][active-model] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Active Job
-----------
Please refer to the [Changelog][active-job] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Active Support
--------------
Please refer to the [Changelog][active-support] for detailed changes.
+### Removals
+
+### Deprecations
+
+### Notable changes
+
Credits
-------
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 69c4a00c5f..5d987264f5 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -719,7 +719,7 @@ Now, the `LoginsController`'s `new` and `create` actions will work as before wit
In addition to "before" filters, you can also run filters after an action has been executed, or both before and after.
-"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running.
+"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. Please note that "after" filters are executed only after a successful action, but not when an exception is raised in the request cycle.
"around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 9673571909..65146ee7da 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -550,7 +550,7 @@ url helper.
<%= user_url(@user, host: 'example.com') %>
```
-NOTE: non-`GET` links require [rails-ujs](https://github.com/rails/rails-ujs) or
+NOTE: non-`GET` links require [rails-ujs](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts) or
[jQuery UJS](https://github.com/rails/jquery-ujs), and won't work in mailer templates.
They will result in normal `GET` requests.
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 77bd3c97e8..b1705855d0 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -117,6 +117,10 @@ Here is a list with all the available Active Record callbacks, listed in the sam
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
+NOTE: `before_destroy` callbacks should be placed before `dependent: :destroy`
+associations (or use the `prepend: true` option), to ensure they execute before
+the records are deleted by `dependent: :destroy`.
+
### `after_initialize` and `after_find`
The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method.
@@ -254,7 +258,11 @@ Halting Execution
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any callback raises an exception, the execution chain gets halted and a ROLLBACK is issued. To intentionally stop a chain use:
+
+```ruby
+throw :abort
+```
WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 2902c5d677..26d01d4ede 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1381,8 +1381,9 @@ class Client < ApplicationRecord
end
```
-NOTE: The `default_scope` is also applied while creating/building a record.
-It is not applied while updating a record. E.g.:
+NOTE: The `default_scope` is also applied while creating/building a record
+when the scope arguments are given as a `Hash`. It is not applied while
+updating a record. E.g.:
```ruby
class Client < ApplicationRecord
@@ -1393,6 +1394,17 @@ Client.new # => #<Client id: nil, active: true>
Client.unscoped.new # => #<Client id: nil, active: nil>
```
+Be aware that, when given in the `Array` format, `default_scope` query arguments
+cannot be converted to a `Hash` for default attribute assignment. E.g.:
+
+```ruby
+class Client < ApplicationRecord
+ default_scope { where("active = ?", true) }
+end
+
+Client.new # => #<Client id: nil, active: nil>
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 34b9c0d2ca..3c61754982 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -333,10 +333,6 @@ As a contributor, it's important to think about whether this API is meant for en
A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly.
-If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project.
-
-A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails.
-
To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first.
Regarding the Rails Stack
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 3b19b0dff1..39f4272b3c 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -132,24 +132,12 @@ learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails.
-You can either open a pull request to [Rails](https://github.com/rails/rails) or
-ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on
-docrails if you contribute regularly.
-Please do not open pull requests in docrails, if you'd like to get feedback on your
-change, ask for it in [Rails](https://github.com/rails/rails) instead.
-
-Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
-
-If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub.
+To do so, open a pull request to [Rails](https://github.com/rails/rails) on GitHub.
When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html).
-NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements.
-
NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
-WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
-
Translating Rails Guides
------------------------
@@ -418,16 +406,6 @@ examples or multiple paragraphs. Otherwise, it's best to make a new paragraph.
Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes.
-### Sanity Check
-
-You should not be the only person who looks at the code before you submit it.
-If you know someone else who uses Rails, try asking them if they'll check out
-your work. If you don't know anyone else using Rails, try hopping into the IRC
-room or posting about your idea to the rails-core mailing list. Doing this in
-private before you push a patch out publicly is the "smoke test" for a patch:
-if you can't convince one other developer of the beauty of your code, you’re
-unlikely to convince the core team either.
-
### Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to Git:
@@ -685,4 +663,4 @@ And then... think about your next contribution!
Rails Contributors
------------------
-All contributions, either via master or docrails, get credit in [Rails Contributors](http://contributors.rubyonrails.org).
+All contributions get credit in [Rails Contributors](http://contributors.rubyonrails.org).
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 180a786237..2276f348a1 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -14,6 +14,7 @@ After reading this guide, you will know:
* How to build features for the engine.
* How to hook the engine into an application.
* How to override engine functionality in the application.
+* Avoid loading Rails frameworks with Load and Configuration Hooks
--------------------------------------------------------------------------------
@@ -1410,3 +1411,114 @@ module MyEngine
end
end
```
+
+Active Support On Load Hooks
+----------------------------
+
+Active Support is the Ruby on Rails component responsible for providing Ruby language extensions, utilities, and other transversal utilities.
+
+Rails code can often be referenced on load of an application. Rails is responsible for the load order of these frameworks, so when you load frameworks, such as `ActiveRecord::Base`, prematurely you are violating an implicit contract your application has with Rails. Moreover, by loading code such as `ActiveRecord::Base` on boot of your application you are loading entire frameworks which may slow down your boot time and could cause conflicts with load order and boot of your application.
+
+On Load hooks are the API that allow you to hook into this initialization process without violating the load contract with Rails. This will also mitigate boot performance degradation and avoid conflicts.
+
+## What are `on_load` hooks?
+
+Since Ruby is a dynamic language, some code will cause different Rails frameworks to load. Take this snippet for instance:
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+This snippet means that when this file is loaded, it will encounter `ActiveRecord::Base`. This encounter causes Ruby to look for the definition of that constant and will require it. This causes the entire Active Record framework to be loaded on boot.
+
+`ActiveSupport.on_load` is a mechanism that can be used to defer the loading of code until it is actually needed. The snippet above can be changed to:
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper }
+```
+
+This new snippet will only include `MyActiveRecordHelper` when `ActiveRecord::Base` is loaded.
+
+## How does it work?
+
+In the Rails framework these hooks are called when a specific library is loaded. For example, when `ActionController::Base` is loaded, the `:action_controller_base` hook is called. This means that all `ActiveSupport.on_load` calls with `:action_controller_base` hooks will be called in the context of `ActionController::Base` (that means `self` will be an `ActionController::Base`).
+
+## Modifying code to use `on_load` hooks
+
+Modifying code is generally straightforward. If you have a line of code that refers to a Rails framework such as `ActiveRecord::Base` you can wrap that code in an `on_load` hook.
+
+### Example 1
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } # self refers to ActiveRecord::Base here, so we can simply #include
+```
+
+### Example 2
+
+```ruby
+ActionController::Base.prepend(MyActionControllerHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:action_controller_base) { prepend MyActionControllerHelper } # self refers to ActionController::Base here, so we can simply #prepend
+```
+
+### Example 3
+
+```ruby
+ActiveRecord::Base.include_root_in_json = true
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { self.include_root_in_json = true } # self refers to ActiveRecord::Base here
+```
+
+## Available Hooks
+
+These are the hooks you can use in your own code.
+
+To hook into the initialization process of one of the following classes use the available hook.
+
+| Class | Available Hooks |
+| --------------------------------- | ------------------------------------ |
+| `ActionCable` | `action_cable` |
+| `ActionController::API` | `action_controller_api` |
+| `ActionController::API` | `action_controller` |
+| `ActionController::Base` | `action_controller_base` |
+| `ActionController::Base` | `action_controller` |
+| `ActionController::TestCase` | `action_controller_test_case` |
+| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` |
+| `ActionMailer::Base` | `action_mailer` |
+| `ActionMailer::TestCase` | `action_mailer_test_case` |
+| `ActionView::Base` | `action_view` |
+| `ActionView::TestCase` | `action_view_test_case` |
+| `ActiveJob::Base` | `active_job` |
+| `ActiveJob::TestCase` | `active_job_test_case` |
+| `ActiveRecord::Base` | `active_record` |
+| `ActiveSupport::TestCase` | `active_support_test_case` |
+| `i18n` | `i18n` |
+
+## Configuration hooks
+
+These are the available configuration hooks. They do not hook into any particular framework, instead they run in context of the entire application.
+
+| Hook | Use Case |
+| ---------------------- | ------------------------------------------------------------------------------------- |
+| `before_configuration` | First configurable block to run. Called before any initializers are run. |
+| `before_initialize` | Second configurable block to run. Called before frameworks initialize. |
+| `before_eager_load` | Third configurable block to run. Does not run if `config.cache_classes` set to false. |
+| `after_initialize` | Last configurable block to run. Called after frameworks initialize. |
+
+### Example
+
+`config.before_configuration { puts 'I am called before any initializers' }`
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 340933c7ee..f25b185fb5 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -20,9 +20,9 @@ Introduction to Rack
Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
-* [Rack API Documentation](http://rack.github.io/)
-
-Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the [Resources](#resources) section below.
+Explaining how Rack works is not really in the scope of this guide. In case you
+are not familiar with Rack's basics, you should check out the [Resources](#resources)
+section below.
Rails on Rack
-------------
@@ -74,7 +74,7 @@ And start the server:
$ rackup config.ru
```
-To find out more about different `rackup` options:
+To find out more about different `rackup` options, you can run:
```bash
$ rackup --help
@@ -89,7 +89,8 @@ Action Dispatcher Middleware Stack
Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application.
-NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements.
+NOTE: `ActionDispatch::MiddlewareStack` is Rails' equivalent of `Rack::Builder`,
+but is built for better flexibility and more features to meet Rails' requirements.
### Inspecting Middleware Stack
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 86492a9332..f7dbbc510e 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -142,16 +142,17 @@ Sometimes, you have a resource that clients always look up without referencing a
get 'profile', to: 'users#show'
```
-Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action but you must also specify the `controller:` to use:
+Passing a `String` to `to:` will expect a `controller#action` format. When using a `Symbol`, the `to:` option should be replaced with `action:`. When using a `String` without a `#`, the `to:` option should be replaced with `controller:`:
```ruby
-get 'profile', to: :show, controller: 'users'
+get 'profile', action: :show, controller: 'users'
```
This resourceful route:
```ruby
resource :geocoder
+resolve('Geocoder') { [:geocoder] }
```
creates six different routes in your application, all mapping to the `Geocoders` controller:
@@ -175,14 +176,6 @@ A singular resourceful route generates these helpers:
As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix.
-WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so:
-
-```ruby
-form_for @geocoder, url: geocoder_path do |f|
-
-# snippet for brevity
-```
-
### Controller Namespaces and Routing
You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router:
@@ -545,7 +538,7 @@ TIP: If you find yourself adding many extra actions to a resourceful route, it's
Non-Resourceful Routes
----------------------
-In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route within your application separately.
+In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route separately within your application.
While you should usually use resourceful routing, there are still many places where the simpler routing is more appropriate. There's no need to try to shoehorn every last piece of your application into a resourceful framework if that's not a good fit.
diff --git a/guides/source/security.md b/guides/source/security.md
index 7e27e6f37d..c305350243 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -257,7 +257,7 @@ protect_from_forgery with: :exception
This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
-NOTE: By default, Rails includes an [unobtrusive scripting adapter](https://github.com/rails/rails-ujs),
+NOTE: By default, Rails includes an [unobtrusive scripting adapter](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts),
which adds a header called `X-CSRF-Token` with the security token on every non-GET
Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails.
When using another library to make Ajax calls, it is necessary to add the security
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index e04b3e3581..cbaf9100f7 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -149,7 +149,7 @@ Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two
parts: the JavaScript half and the Ruby half.
Unless you have disabled the Asset Pipeline,
-[rails-ujs](https://github.com/rails/rails-ujs/blob/master/src/rails-ujs.coffee)
+[rails-ujs](https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs.coffee)
provides the JavaScript half, and the regular Ruby view helpers add appropriate
tags to your DOM.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 66873748ce..6032d2e1a1 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,9 +1 @@
-* Specify form field ids when generating a scaffold.
-
- This makes sure that the labels are linked up with the fields. The
- regression was introduced when the template was switched to
- `form_with`.
-
- *Yves Senn*
-
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 00add5829d..6d8bf28943 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -87,8 +87,8 @@ module Rails
# groups assets: [:development, :test]
#
# # Returns
- # # => [:default, :development, :assets] for Rails.env == "development"
- # # => [:default, :production] for Rails.env == "production"
+ # # => [:default, "development", :assets] for Rails.env == "development"
+ # # => [:default, "production"] for Rails.env == "production"
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 5c833e12ba..3bd18ebfb5 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -16,7 +16,7 @@ module Rails
add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
add_gem_filters
- add_silencer { |line| line !~ APP_DIRS_PATTERN }
+ add_silencer { |line| !APP_DIRS_PATTERN.match?(line) }
end
private
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 9c4bd16aad..70dce268f1 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -7,7 +7,8 @@ class CodeStatistics #:nodoc:
"Model tests",
"Mailer tests",
"Job tests",
- "Integration tests"]
+ "Integration tests",
+ "System tests"]
HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" }
diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index 588fb06b15..5bfbe58d97 100644
--- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -140,7 +140,7 @@ module Rails
class_option :mode, enum: %w( html list line column ), type: :string,
desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)."
- class_option :header, type: :string
+ class_option :header, type: :boolean
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this console under (test/development/production)."
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 46001f306a..aef66adb64 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -150,7 +150,7 @@ module Rails
end
def field_id(attribute_name)
- [singular_table_name, attribute_name].join('_')
+ [singular_table_name, attribute_name].join("_")
end
def singular_table_name # :doc:
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index 25870f19c8..4206002a1b 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -1,7 +1,7 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
@@ -11,9 +11,6 @@
// about supported directives.
//
<% unless options[:skip_javascript] -%>
-<% if options[:javascript] -%>
-//= require <%= options[:javascript] %>
-<% end -%>
//= require rails-ujs
<% unless options[:skip_turbolinks] -%>
//= require turbolinks
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 865300bef9..d05ea0f511 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -2,7 +2,7 @@
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index c6607dbb2b..52b3de5ee5 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -19,9 +19,9 @@ chdir APP_ROOT do
<% unless options[:skip_yarn] %>
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
-
<% end %>
<% unless options.skip_active_record -%>
+
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d23af018c7..d385b363c6 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -17,6 +17,7 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
<% unless options.skip_active_record -%>
+
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 511b4a82eb..b75b65c8df 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -13,6 +13,7 @@ Rails.application.configure do
config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
+ # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index af3be10a31..6bdb673215 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -205,7 +205,14 @@ module Rails
# Returns all expanded paths but only if they exist in the filesystem.
def existent
- expanded.select { |f| File.exist?(f) }
+ expanded.select do |f|
+ does_exist = File.exist?(f)
+
+ if !does_exist && File.symlink?(f)
+ raise "File #{f.inspect} is a symlink that does not point to a valid file"
+ end
+ does_exist
+ end
end
def existent_directories
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 90927159dd..eb2c578f91 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -262,5 +262,13 @@ module ApplicationTests
Rails.env = orig_rails_env if orig_rails_env
end
end
+
+ test "connections checked out during initialization are returned to the pool" do
+ app_file "config/initializers/active_record.rb", <<-RUBY
+ ActiveRecord::Base.connection
+ RUBY
+ require "#{app_path}/config/environment"
+ assert !ActiveRecord::Base.connection_pool.active_connection?
+ end
end
end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 00f6620188..449d281967 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -77,7 +77,7 @@ module ApplicationTests
assert_equal "Schema migrations table does not exist yet.\n", output
end
- test "test migration status" do
+ test "migration status" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -117,7 +117,7 @@ module ApplicationTests
end
end
- test "test migration status after rollback and redo" do
+ test "migration status after rollback and redo" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -224,7 +224,7 @@ module ApplicationTests
end
end
- test "test migration status migrated file is deleted" do
+ test "migration status migrated file is deleted" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index a8e3a7ec5b..23b259b503 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -323,7 +323,7 @@ module ApplicationTests
assert true
end
- test "test line filter does not run this" do
+ test "line filter does not run this" do
assert true
end
end
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 7b2551062a..f3db0a51d2 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -274,3 +274,23 @@ class PathsTest < ActiveSupport::TestCase
end
end
end
+
+class PathsIntegrationTest < ActiveSupport::TestCase
+ test "A failed symlink is still a valid file" do
+ Dir.mktmpdir do |dir|
+ Dir.chdir(dir) do
+ FileUtils.mkdir_p("foo")
+ File.symlink("foo/doesnotexist.rb", "foo/bar.rb")
+ assert_equal true, File.symlink?("foo/bar.rb")
+
+ root = Rails::Paths::Root.new("foo")
+ root.add "bar.rb"
+
+ exception = assert_raises(RuntimeError) do
+ root["bar.rb"].existent
+ end
+ assert_match File.expand_path("foo/bar.rb"), exception.message
+ end
+ end
+ end
+end