aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock16
-rw-r--r--Rakefile1
-rw-r--r--actionmailer/lib/action_mailer/base.rb5
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb6
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb12
-rw-r--r--actionmailer/test/delivery_methods_test.rb4
-rw-r--r--actionpack/CHANGELOG.md5
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb12
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb2
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb15
-rw-r--r--actionpack/lib/action_controller/metal/live.rb9
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb6
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb3
-rw-r--r--actionpack/lib/action_controller/test_case.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb10
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb2
-rw-r--r--actionpack/test/controller/rescue_test.rb12
-rw-r--r--actionpack/test/controller/test_case_test.rb4
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb12
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb18
-rw-r--r--actionpack/test/dispatch/request_test.rb4
-rw-r--r--actionpack/test/dispatch/response_test.rb2
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb2
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb12
-rw-r--r--actionpack/test/dispatch/static_test.rb2
-rw-r--r--actionview/CHANGELOG.md25
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb2
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/tags/week_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb14
-rw-r--r--actionview/lib/action_view/lookup_context.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionview/lib/action_view/renderer/renderer.rb2
-rw-r--r--actionview/lib/action_view/template.rb2
-rw-r--r--actionview/lib/action_view/template/error.rb19
-rw-r--r--actionview/test/fixtures/test/_klass.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb2
-rw-r--r--actionview/test/template/date_helper_test.rb20
-rw-r--r--actionview/test/template/form_helper_test.rb19
-rw-r--r--actionview/test/template/render_test.rb10
-rw-r--r--actionview/test/template/template_error_test.rb25
-rw-r--r--actionview/test/template/translation_helper_test.rb8
-rw-r--r--actionview/test/template/url_helper_test.rb31
-rw-r--r--activejob/README.md2
-rw-r--r--activejob/lib/active_job/arguments.rb25
-rw-r--r--activejob/lib/active_job/async_job.rb5
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/test/adapters/async.rb1
-rw-r--r--activejob/test/jobs/rescue_job.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activerecord/CHANGELOG.md233
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb22
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb8
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb32
-rw-r--r--activerecord/lib/active_record/core.rb4
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb38
-rw-r--r--activerecord/lib/active_record/migration.rb117
-rw-r--r--activerecord/lib/active_record/model_schema.rb3
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake14
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb45
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb2
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb10
-rw-r--r--activerecord/lib/active_record/sanitization.rb16
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb8
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/type/type_map.rb2
-rw-r--r--activerecord/lib/active_record/type_caster.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb2
-rw-r--r--activerecord/lib/active_record/validations/associated.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb41
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb50
-rw-r--r--activerecord/test/cases/adapters/mysql/sp_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql2/sp_test.rb23
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb46
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb12
-rw-r--r--activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb41
-rw-r--r--activerecord/test/cases/associations/eager_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb25
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb79
-rw-r--r--activerecord/test/cases/attributes_test.rb13
-rw-r--r--activerecord/test/cases/autosave_association_test.rb36
-rw-r--r--activerecord/test/cases/base_test.rb81
-rw-r--r--activerecord/test/cases/batches_test.rb2
-rw-r--r--activerecord/test/cases/connection_management_test.rb9
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb14
-rw-r--r--activerecord/test/cases/inheritance_test.rb81
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb54
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb4
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb24
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb2
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb102
-rw-r--r--activerecord/test/cases/migrator_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb16
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb28
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb32
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb21
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb2
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb12
-rw-r--r--activerecord/test/cases/view_test.rb14
-rw-r--r--activerecord/test/fixtures/content.yml3
-rw-r--r--activerecord/test/fixtures/content_positions.yml3
-rw-r--r--activerecord/test/models/bulb.rb1
-rw-r--r--activerecord/test/models/car.rb1
-rw-r--r--activerecord/test/models/content.rb40
-rw-r--r--activerecord/test/models/guitar.rb4
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/models/project.rb8
-rw-r--r--activerecord/test/models/tuning_peg.rb4
-rw-r--r--activerecord/test/schema/schema.rb19
-rw-r--r--activerecord/test/support/schema_dumping_helper.rb2
-rw-r--r--activesupport/CHANGELOG.md31
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb17
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb30
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb66
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb35
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb5
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb41
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/dependencies.rb8
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb2
-rw-r--r--activesupport/lib/active_support/file_evented_update_checker.rb147
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb19
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb31
-rw-r--r--activesupport/lib/active_support/key_generator.rb6
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb3
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb4
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb2
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb46
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb2
-rw-r--r--activesupport/test/caching_test.rb38
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb6
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb6
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb21
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb38
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb19
-rw-r--r--activesupport/test/file_evented_update_checker_test.rb150
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb241
-rw-r--r--activesupport/test/file_update_checker_test.rb113
-rw-r--r--activesupport/test/inflector_test.rb22
-rw-r--r--activesupport/test/inflector_test_cases.rb34
-rw-r--r--activesupport/test/share_lock_test.rb2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/active_job_basics.md2
-rw-r--r--guides/source/active_model_basics.md6
-rw-r--r--guides/source/active_record_migrations.md2
-rw-r--r--guides/source/active_record_querying.md56
-rw-r--r--guides/source/active_record_validations.md15
-rw-r--r--guides/source/active_support_core_extensions.md21
-rw-r--r--guides/source/active_support_instrumentation.md3
-rw-r--r--guides/source/api_app.md4
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/association_basics.md18
-rw-r--r--guides/source/autoloading_and_reloading_constants.md2
-rw-r--r--guides/source/configuring.md30
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md4
-rw-r--r--guides/source/engines.md21
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/i18n.md2
-rw-r--r--guides/source/initialization.md3
-rw-r--r--guides/source/plugins.md2
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/testing.md60
-rw-r--r--guides/source/upgrading_ruby_on_rails.md8
-rw-r--r--railties/CHANGELOG.md63
-rw-r--r--railties/Rakefile29
-rw-r--r--railties/lib/rails/application.rb7
-rw-r--r--railties/lib/rails/application/configuration.rb46
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb4
-rw-r--r--railties/lib/rails/code_statistics.rb2
-rw-r--r--railties/lib/rails/code_statistics_calculator.rb1
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb33
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt33
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore1
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb2
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/test_unit/reporter.rb10
-rw-r--r--railties/test/application/assets_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb51
-rw-r--r--railties/test/application/middleware/sendfile_test.rb2
-rw-r--r--railties/test/application/middleware/static_test.rb8
-rw-r--r--railties/test/application/middleware_test.rb4
-rw-r--r--railties/test/application/rake/dbs_test.rb52
-rw-r--r--railties/test/application/rake/migrations_test.rb22
-rw-r--r--railties/test/application/test_runner_test.rb2
-rw-r--r--railties/test/code_statistics_calculator_test.rb16
-rw-r--r--railties/test/generators/actions_test.rb15
-rw-r--r--railties/test/generators/app_generator_test.rb12
-rw-r--r--railties/test/generators/namespaced_generators_test.rb24
-rw-r--r--railties/test/railties/engine_test.rb2
-rw-r--r--railties/test/test_unit/reporter_test.rb8
-rw-r--r--tasks/release.rb23
283 files changed, 3556 insertions, 1046 deletions
diff --git a/Gemfile b/Gemfile
index 260604f570..b9fc37cba2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -45,6 +45,7 @@ end
# Active Support.
gem 'dalli', '>= 2.2.1'
+gem 'listen', '~> 3.0.4'
# Active Job.
group :job do
diff --git a/Gemfile.lock b/Gemfile.lock
index 58d2941170..f8d6d5d24b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -137,7 +137,7 @@ PATH
activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha)
- concurrent-ruby (~> 1.0.0.pre3, < 2.0.0)
+ concurrent-ruby (~> 1.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
method_source
@@ -176,7 +176,7 @@ GEM
builder (3.2.2)
bunny (2.2.0)
amq-protocol (>= 2.0.0)
- byebug (6.0.2)
+ byebug (8.2.0)
celluloid (0.17.2)
celluloid-essentials
celluloid-extras
@@ -201,7 +201,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.9.1.1)
- concurrent-ruby (1.0.0.pre4)
+ concurrent-ruby (1.0.0)
connection_pool (2.2.0)
dalli (2.7.4)
dante (0.2.0)
@@ -212,6 +212,9 @@ GEM
delayed_job (>= 3.0, < 5)
erubis (2.7.0)
execjs (2.6.0)
+ ffi (1.9.10)
+ ffi (1.9.10-x64-mingw32)
+ ffi (1.9.10-x86-mingw32)
hitimes (1.2.3)
hitimes (1.2.3-x86-mingw32)
i18n (0.7.0)
@@ -219,6 +222,9 @@ GEM
kindlerb (0.1.1)
mustache
nokogiri
+ listen (3.0.4)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
loofah (2.0.3)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
@@ -252,6 +258,9 @@ GEM
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
rake (10.4.2)
+ rb-fsevent (0.9.6)
+ rb-inotify (0.9.5)
+ ffi (>= 0.5.0)
rdoc (4.2.0)
redcarpet (3.2.3)
redis (3.2.1)
@@ -333,6 +342,7 @@ DEPENDENCIES
jquery-rails!
json
kindlerb (= 0.1.1)
+ listen (~> 3.0.4)
mail!
minitest (< 5.3.4)
mocha (~> 0.14)
diff --git a/Rakefile b/Rakefile
index db27105cc3..2ec39a1c85 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,4 +1,3 @@
-require 'sdoc'
require 'net/http'
$:.unshift File.expand_path('..', __FILE__)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index c9e7f7d0d3..cbd7cec70f 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -441,8 +441,6 @@ module ActionMailer
helper ActionMailer::MailHelper
- private_class_method :new #:nodoc:
-
class_attribute :default_params
self.default_params = {
mime_version: "1.0",
@@ -580,11 +578,10 @@ module ActionMailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
- def initialize(method_name=nil, *args)
+ def initialize
super()
@_mail_was_called = false
@_message = Mail.new
- process(method_name, *args) if method_name
end
def process(method_name, *args) #:nodoc:
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 622d481113..5fcb5a0c88 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -21,7 +21,11 @@ module ActionMailer
end
def __getobj__ #:nodoc:
- @obj ||= @mailer.send(:new, @mail_method, *@args).message
+ @obj ||= begin
+ mailer = @mailer.new
+ mailer.process @mail_method, *@args
+ mailer.message
+ end
end
def __setobj__(obj) #:nodoc:
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 45cfe16899..e423aac389 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -40,7 +40,7 @@ module ActionMailer
end
end
- # Assert that no emails have been sent.
+ # Asserts that no emails have been sent.
#
# def test_emails
# assert_no_emails
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 85d3629514..285b2cfcb5 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -8,6 +8,12 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
+module Rails
+ def self.root
+ File.expand_path('../', File.dirname(__FILE__))
+ end
+end
+
require 'active_support/testing/autorun'
require 'active_support/testing/method_call_assertions'
require 'action_mailer'
@@ -26,12 +32,6 @@ I18n.enforce_available_locales = false
FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__))
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
-module Rails
- def self.root
- File.expand_path('../', File.dirname(__FILE__))
- end
-end
-
# Skips the current run on Rubinius using Minitest::Assertions#skip
def rubinius_skip(message = '')
skip message if RUBY_ENGINE == 'rbx'
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index d17e774092..2786fe0d07 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -31,8 +31,8 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
assert_equal settings, ActionMailer::Base.smtp_settings
end
- test "default file delivery settings" do
- settings = {location: "#{Dir.tmpdir}/mails"}
+ test "default file delivery settings (with Rails.root)" do
+ settings = {location: "#{Rails.root}/tmp/mails"}
assert_equal settings, ActionMailer::Base.file_settings
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4f95a9bab9..40f447ada7 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -17,11 +17,6 @@
*Agis Anastasopoulos*
-* Deprecate `config.static_cache_control` in favor of
- `config.public_file_server.headers`
-
- *Yuki Nishijima*
-
* Add the ability of returning arbitrary headers to ActionDispatch::Static
Now ActionDispatch::Static can accept HTTP headers so that developers
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 4501202b8c..7f349f2741 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -149,7 +149,7 @@ module AbstractController
# ==== Parameters
# * <tt>action_name</tt> - The name of an action to be tested
def available_action?(action_name)
- _find_action_name(action_name).present?
+ _find_action_name(action_name)
end
# Returns true if the given controller is capable of rendering
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 13795f0dd8..d5317e4717 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -39,8 +39,8 @@ module AbstractController
# except: :index, if: -> { true } # the :except option will be ignored.
#
# ==== Options
- # * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
+ # * <tt>only</tt> - The callback should be run only for this action.
+ # * <tt>except</tt> - The callback should be run for all actions except this action.
def _normalize_callback_options(options)
_normalize_callback_option(options, :only, :if)
_normalize_callback_option(options, :except, :unless)
@@ -59,9 +59,9 @@ module AbstractController
# * <tt>names</tt> - A list of valid names that could be used for
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
- # using #skip_action_callback
+ # using #skip_action_callback.
def skip_action_callback(*names)
- ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in the next major version of Rails. Please use skip_before_action, skip_after_action or skip_around_action instead.')
+ ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.')
skip_before_action(*names, raise: false)
skip_after_action(*names, raise: false)
skip_around_action(*names, raise: false)
@@ -82,8 +82,8 @@ module AbstractController
# * <tt>block</tt> - A proc that should be added to the callbacks.
#
# ==== Block Parameters
- # * <tt>name</tt> - The callback to be added
- # * <tt>options</tt> - A hash of options to be used when adding the callback
+ # * <tt>name</tt> - The callback to be added.
+ # * <tt>options</tt> - A hash of options to be used when adding the callback.
def _insert_callbacks(callbacks, block = nil)
options = callbacks.extract_options!
_normalize_callback_options(options)
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 89d589c486..d86a793e4c 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -66,7 +66,7 @@ module ActionController
#
# You can also pass an object that responds to +maximum+, such as a
# collection of active records. In this case +last_modified+ will be set by
- # calling +maximum(:updated_at)+ on the collection (the timestamp of the
+ # calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
# most recently updated record) and the +etag+ by passing the object itself.
#
# def index
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 5260dc0336..5c0ada37be 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -3,12 +3,19 @@ module ActionController
end
class BadRequest < ActionControllerError #:nodoc:
- attr_reader :original_exception
-
def initialize(msg = nil, e = nil)
+ if e
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
super(msg)
- @original_exception = e
- set_backtrace e.backtrace if e
+ set_backtrace $!.backtrace if $!
+ end
+
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 7db8d13e24..27b3eb4e58 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -36,8 +36,9 @@ module ActionController
extend ActiveSupport::Concern
module ClassMethods
- def make_response!(request)
- if request.env["HTTP_VERSION"] == "HTTP/1.0"
+ def make_response!(response)
+ request = response.request
+ if request.get_header("HTTP_VERSION") == "HTTP/1.0"
super
else
Live::Response.new.tap do |res|
@@ -293,8 +294,8 @@ module ActionController
response.close if response
end
- def set_response!(request)
- @_response = self.class.make_response! request
+ def set_response!(response)
+ @_response = self.class.make_response! response
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index 68cc9a9c9b..81b9a7b9ed 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -7,10 +7,8 @@ module ActionController #:nodoc:
include ActiveSupport::Rescuable
def rescue_with_handler(exception)
- if (exception.respond_to?(:original_exception) &&
- (orig_exception = exception.original_exception) &&
- handler_for_rescue(orig_exception))
- exception = orig_exception
+ if exception.cause && handler_for_rescue(exception.cause)
+ exception = exception.cause
end
super(exception)
end
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index b2b3b4283f..ac37b00010 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -4,9 +4,6 @@ module ActionController
# Behavior specific to functional tests
module Functional # :nodoc:
- def set_response!(request)
- end
-
def recycle!
@_url_options = nil
self.formats = nil
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 380e9d29b4..442ffd6d7c 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -210,11 +210,11 @@ module ActionController
# # Simulate a POST response with the given HTTP parameters.
# post(:create, params: { book: { title: "Love Hina" }})
#
- # # Assert that the controller tried to redirect us to
+ # # Asserts that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
- # # Assert that the controller really put the book in the database.
+ # # Asserts that the controller really put the book in the database.
# assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
@@ -509,20 +509,19 @@ module ActionController
end
end
- @controller.request = @request
- @controller.response = @response
-
@request.fetch_header("SCRIPT_NAME") do |k|
@request.set_header k, @controller.config.relative_url_root
end
@controller.recycle!
- @controller.process(action)
+ @controller.dispatch(action, @request, @response)
+ @request = @controller.request
+ @response = @controller.response
@request.delete_header 'HTTP_COOKIE'
if @request.have_cookie_jar?
- unless @response.committed?
+ unless @request.cookie_jar.committed?
@request.cookie_jar.write(@response)
self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 248ecfd676..c9df787351 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -55,11 +55,11 @@ module ActionDispatch
begin
strategy.call(raw_post)
- rescue => e # JSON or Ruby code block errors
+ rescue # JSON or Ruby code block errors
my_logger = logger || ActiveSupport::Logger.new($stderr)
my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
- raise ParamsParser::ParseError.new(e.message, e)
+ raise ParamsParser::ParseError
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 35e3ac304f..ea61ad0c02 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -344,7 +344,7 @@ module ActionDispatch
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
end
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
- raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}", e)
+ raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
end
alias :query_parameters :GET
@@ -360,7 +360,7 @@ module ActionDispatch
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
- raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}", e)
+ raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
end
alias :request_parameters :POST
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index c54efb6541..f0127aa276 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -149,7 +149,6 @@ module ActionDispatch # :nodoc:
self.body, self.status = body, status
- @blank = false
@cv = new_cond
@committed = false
@sending = false
@@ -287,12 +286,8 @@ module ActionDispatch # :nodoc:
@stream.write string
end
- EMPTY = " "
-
# Allows you to manually set or override the response body.
def body=(body)
- @blank = true if body == EMPTY
-
if body.respond_to?(:to_path)
@stream = body
else
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 5fd984cd07..3b61824cc9 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -37,7 +37,7 @@ module ActionDispatch
@backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
- expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
+ expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
end
def rescue_template
@@ -106,17 +106,13 @@ module ActionDispatch
end
def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
+ if @@rescue_responses.has_key?(exception.cause.class.name)
+ exception.cause
else
exception
end
end
- def registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
- end
-
def clean_backtrace(*args)
if backtrace_cleaner
backtrace_cleaner.clean(backtrace, *args)
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 18af0a583a..c2a4f46e67 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -10,11 +10,25 @@ module ActionDispatch
# Raised when raw data from the request cannot be parsed by the parser
# defined for request's content mime type.
class ParseError < StandardError
- attr_reader :original_exception
- def initialize(message, original_exception)
- super(message)
- @original_exception = original_exception
+ def initialize(message = nil, original_exception = nil)
+ if message
+ ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \
+ "#{self.class} will automatically capture the message " \
+ "of the original exception.", caller)
+ end
+
+ if original_exception
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
+ super($!.message)
+ end
+
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 9e50fea3fc..5fb5953811 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -7,14 +7,22 @@ require 'action_dispatch/request/session'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
- attr_reader :original_exception
- def initialize(const_error)
- @original_exception = const_error
+ def initialize(const_error = nil)
+ if const_error
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
super("Session contains objects whose class definition isn't available.\n" +
"Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n")
+ "(Original exception: #{$!.message} [#{$!.class}])\n")
+ set_backtrace $!.backtrace
+ end
+
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
@@ -59,8 +67,8 @@ module ActionDispatch
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
- rescue LoadError, NameError => e
- raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
+ rescue LoadError, NameError
+ raise ActionDispatch::Session::SessionRestoreError
end
retry
else
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 90e2ae6802..44fc1ee736 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -15,7 +15,11 @@ module ActionDispatch
def name; klass.name; end
def inspect
- klass.to_s
+ if klass.is_a?(Class)
+ klass.to_s
+ else
+ klass.class.to_s
+ end
end
def build(app)
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 75f8e05a3f..ea9ab3821d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -15,7 +15,6 @@ module ActionDispatch
class FileHandler
def initialize(root, index: 'index', headers: {})
@root = root.chomp('/')
- @compiled_root = /^#{Regexp.escape(root)}/
@file_server = ::Rack::File.new(@root, headers)
@index = index
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index c1e8b6cae3..5060da9369 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -1,6 +1,6 @@
<header>
<h1>
- <%= @exception.original_exception.class.to_s %> in
+ <%= @exception.cause.class.to_s %> in
<%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
</h1>
</header>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
index 77bcd26726..78d52acd96 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
@@ -1,4 +1,4 @@
-<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
+<%= @exception.cause.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index b6e21b0d28..eab20b075d 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -21,10 +21,10 @@ module ActionDispatch
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
#
- # # assert that the response was a redirection
+ # # Asserts that the response was a redirection
# assert_response :redirect
#
- # # assert that the response code was status code 401 (unauthorized)
+ # # Asserts that the response code was status code 401 (unauthorized)
# assert_response 401
def assert_response(type, message = nil)
if Symbol === type
@@ -42,20 +42,20 @@ module ActionDispatch
end
end
- # Assert that the redirection options passed in match those of the redirect called in the latest action.
+ # Asserts that the redirection options passed in match those of the redirect called in the latest action.
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
#
- # # assert that the redirection was to the "index" action on the WeblogController
+ # # Asserts that the redirection was to the "index" action on the WeblogController
# assert_redirected_to controller: "weblog", action: "index"
#
- # # assert that the redirection was to the named route login_url
+ # # Asserts that the redirection was to the named route login_url
# assert_redirected_to login_url
#
- # # assert that the redirection was to the url for @customer
+ # # Asserts that the redirection was to the url for @customer
# assert_redirected_to @customer
#
- # # asserts that the redirection matches the regular expression
+ # # Asserts that the redirection matches the regular expression
# assert_redirected_to %r(\Ahttp://example.org)
def assert_redirected_to(options = {}, message=nil)
assert_response(:redirect, message)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 54e24ed6bf..78ef860548 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -14,14 +14,14 @@ module ActionDispatch
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
# and a :method containing the required HTTP verb.
#
- # # assert that POSTing to /items will call the create action on ItemsController
+ # # Asserts that POSTing to /items will call the create action on ItemsController
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
- # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
+ # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the
# extras argument, appending the query string on the path directly will not work. For example:
#
- # # assert that a path of '/items/list/1?view=print' returns the correct options
+ # # Asserts that a path of '/items/list/1?view=print' returns the correct options
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
@@ -104,13 +104,13 @@ module ActionDispatch
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
- # # Assert a basic route: a controller with the default action (index)
+ # # Asserts a basic route: a controller with the default action (index)
# assert_routing '/home', controller: 'home', action: 'index'
#
# # Test a route generated with a specific controller, action, and parameter (id)
# assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
#
- # # Assert a basic route (controller + default action), with an error message if it fails
+ # # Asserts a basic route (controller + default action), with an error message if it fails
# assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
#
# # Tests a route, providing a defaults hash
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 7e59bb68cf..790f9ea5d2 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -131,35 +131,35 @@ module ActionDispatch
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def get_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
request_via_redirect(:get, path, *args)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def post_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
request_via_redirect(:post, path, *args)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def patch_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
request_via_redirect(:patch, path, *args)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
request_via_redirect(:put, path, *args)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def delete_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.')
request_via_redirect(:delete, path, *args)
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 4224ac2a1b..843dafac06 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
Thread.abort_on_exception = true
module ActionController
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 7835d2768a..6ae33be3c8 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -170,7 +170,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_with_view_runtime
get :show
wait
- assert_match(/Completed 200 OK in [\d]ms/, logs[1])
+ assert_match(/Completed 200 OK in \d+ms/, logs[1])
end
def test_append_info_to_payload_is_called_even_with_exception
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index f53f061e10..f42bef883f 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -132,11 +132,19 @@ class RescueController < ActionController::Base
end
def io_error_in_view
- raise ActionView::TemplateError.new(nil, IOError.new('this is io error'))
+ begin
+ raise IOError.new('this is io error')
+ rescue
+ raise ActionView::TemplateError.new(nil)
+ end
end
def zero_division_error_in_view
- raise ActionView::TemplateError.new(nil, ZeroDivisionError.new('this is zero division error'))
+ begin
+ raise ZeroDivisionError.new('this is zero division error')
+ rescue
+ raise ActionView::TemplateError.new(nil)
+ end
end
protected
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 40c97abd35..e50373a0cc 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -75,7 +75,7 @@ class TestCaseTest < ActionController::TestCase
end
def test_headers
- render plain: request.headers.env.to_json
+ render plain: ::JSON.dump(request.headers.env)
end
def test_html_output
@@ -139,7 +139,7 @@ XML
def delete_cookie
cookies.delete("foo")
- head :ok
+ render plain: 'ok'
end
def test_without_body
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 93258fbceb..8960156d00 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -42,7 +42,11 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
- raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ begin
+ raise AbstractController::ActionNotFound.new
+ rescue
+ raise ActionView::Template::Error.new('template')
+ end
when "/missing_template"
raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer')
when "/bad_request"
@@ -56,12 +60,12 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
when "/syntax_error_into_view"
begin
eval 'broke_syntax ='
- rescue Exception => e
+ rescue Exception
template = ActionView::Template.new(File.read(__FILE__),
__FILE__,
ActionView::Template::Handlers::Raw.new,
{})
- raise ActionView::Template::Error.new(template, e)
+ raise ActionView::Template::Error.new(template)
end
when "/framework_raises"
method_that_raises
@@ -360,7 +364,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_select 'pre code a:first', %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call}
end
- # assert framework trace that that threw the error is first
+ # assert framework trace that threw the error is first
assert_select '#Framework-Trace' do
assert_select 'pre code a:first', /method_that_raises/
end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 55becc1c91..160cdc1582 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
module ActionController
module Live
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index c2300a0142..a3992ad008 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -37,6 +37,13 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
)
end
+ test "parses json params for application/vnd.api+json" do
+ assert_parses(
+ {"person" => {"name" => "David"}},
+ "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/vnd.api+json' }
+ )
+ end
+
test "nils are stripped from collections" do
assert_parses(
{"person" => []},
@@ -69,8 +76,8 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
$stderr = StringIO.new # suppress the log
json = "[\"person]\": {\"name\": \"David\"}}"
exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => false} }
- assert_equal JSON::ParserError, exception.original_exception.class
- assert_equal exception.original_exception.message, exception.message
+ assert_equal JSON::ParserError, exception.cause.class
+ assert_equal exception.cause.message, exception.message
ensure
$stderr = STDERR
end
@@ -136,6 +143,13 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
)
end
+ test "parses json params for application/vnd.api+json" do
+ assert_parses(
+ {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/vnd.api+json' }
+ )
+ end
+
test "parses json with non-object JSON content" do
assert_parses(
{"user" => {"_json" => "string content" }, "_json" => "string content" },
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index e9896a71f4..22240699d9 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -1012,8 +1012,8 @@ class RequestParameters < BaseRequestTest
request.parameters
end
- assert e.original_exception
- assert_equal e.original_exception.backtrace, e.backtrace
+ assert_not_nil e.cause
+ assert_equal e.cause.backtrace, e.backtrace
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 126379a23c..981d820ccf 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -206,8 +206,6 @@ class ResponseTest < ActiveSupport::TestCase
end
test "read content type with charset utf-16" do
- jruby_skip "https://github.com/jruby/jruby/issues/3138"
-
original = ActionDispatch::Response.default_charset
begin
ActionDispatch::Response.default_charset = 'utf-16'
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
index 361ceca677..7ef513b0c8 100644
--- a/actionpack/test/dispatch/routing/concerns_test.rb
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -109,8 +109,6 @@ class RoutingConcernsTest < ActionDispatch::IntegrationTest
end
def test_concerns_executes_block_in_context_of_current_mapper
- jruby_skip "https://github.com/jruby/jruby/issues/3143"
-
mapper = ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new)
mapper.concern :test_concern do
resources :things
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 15dd702161..ffdf775836 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -9,13 +9,21 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
when "/not_found"
raise AbstractController::ActionNotFound
when "/bad_params"
- raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new)
+ begin
+ raise StandardError.new
+ rescue
+ raise ActionDispatch::ParamsParser::ParseError
+ end
when "/method_not_allowed"
raise ActionController::MethodNotAllowed, 'PUT'
when "/unknown_http_method"
raise ActionController::UnknownHttpMethod
when "/not_found_original_exception"
- raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ begin
+ raise AbstractController::ActionNotFound.new
+ rescue
+ raise ActionView::Template::Error.new('template')
+ end
else
raise "puke!"
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index e62ed09b0a..1da57ab50b 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -75,8 +75,6 @@ module StaticTests
end
def test_served_static_file_with_non_english_filename
- jruby_skip "Stop skipping if following bug gets fixed: " \
- "http://jira.codehaus.org/browse/JRUBY-7192"
assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index c010b7ce91..0981d47d7a 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,26 @@
+* Respect value of `:object` if `:object` is false when rendering.
+
+ Fixes #22260.
+
+ *Yuichiro Kaneko*
+
+* Generate `week_field` input values using a 1-based index and not a 0-based index
+ as per the W3 spec: http://www.w3.org/TR/html-markup/datatypes.html#form.data.week
+
+ *Christoph Geschwind*
+
+* Allow `host` option in `javascript_include_tag` and `stylesheet_link_tag` helpers
+
+ *Grzegorz Witek*
+
+* Restrict `url_for :back` to valid, non-JavaScript URLs. GH#14444
+
+ *Damien Burke*
+
+* Allow `date_select` helper selected option to accept hash like the default options.
+
+ *Lecky Lao*
+
* Collection input propagates input's `id` to the label's `for` attribute when
using html options as the last element of collection.
@@ -29,7 +52,7 @@
* Add a break_sequence option to word_wrap so you can specify a custom break.
- * Mauricio Gomez *
+ *Mauricio Gomez*
* Add wildcard matching to explicit dependencies.
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 7716955fd9..5a4c3ea3fe 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
require 'action_view/path_set'
module ActionView
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 12e9723a02..6f2f9ca53c 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
require 'action_view/dependency_tracker'
require 'monitor'
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index fa46a22500..2ffed6b395 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -55,7 +55,7 @@ module ActionView
# # => <script src="http://www.example.com/xmlhr.js"></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!('protocol', 'extname').symbolize_keys
+ path_options = options.extract!('protocol', 'extname', 'host').symbolize_keys
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
@@ -91,7 +91,7 @@ module ActionView
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!('protocol').symbolize_keys
+ path_options = options.extract!('protocol', 'host').symbolize_keys
sources.uniq.map { |source|
tag_options = {
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 312e41ee48..233e613e97 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -845,7 +845,12 @@ module ActionView
private
%w( sec min hour day month year ).each do |method|
define_method(method) do
- @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
+ case @datetime
+ when Hash then @datetime[method.to_sym]
+ when Numeric then @datetime
+ when nil then nil
+ else @datetime.send(method)
+ end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/week_field.rb b/actionview/lib/action_view/helpers/tags/week_field.rb
index 5b3d0494e9..835d1667d7 100644
--- a/actionview/lib/action_view/helpers/tags/week_field.rb
+++ b/actionview/lib/action_view/helpers/tags/week_field.rb
@@ -5,7 +5,7 @@ module ActionView
private
def format_date(value)
- value.try(:strftime, "%Y-W%W")
+ value.try(:strftime, "%Y-W%V")
end
end
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index dde1ef22ac..4c4d2c4457 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -90,7 +90,7 @@ module ActionView
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
title = "translation missing: #{keys.join('.')}"
- interpolations = options.except(:default)
+ interpolations = options.except(:default, :scope)
if interpolations.any?
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ')
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 5684de35e8..baebc34b4b 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -41,11 +41,21 @@ module ActionView
end
def _back_url # :nodoc:
- referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"]
- referrer || 'javascript:history.back()'
+ _filtered_referrer || 'javascript:history.back()'
end
protected :_back_url
+ def _filtered_referrer # :nodoc:
+ if controller.respond_to?(:request)
+ referrer = controller.request.env["HTTP_REFERER"]
+ if referrer && URI(referrer).scheme != 'javascript'
+ referrer
+ end
+ end
+ rescue URI::InvalidURIError
+ end
+ protected :_filtered_referrer
+
# Creates an anchor element of the given +name+ using a URL created by the set of +options+.
# See the valid options in the documentation for +url_for+. It's also possible to
# pass a String instead of an options hash, which generates an anchor element that uses the
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index ec6edfaaa3..d3935788ef 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/module/attribute_accessors'
require 'action_view/template/resolver'
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 39c8658ffe..bdbf03191a 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,5 @@
require 'action_view/renderer/partial_renderer/collection_caching'
-require 'concurrent'
+require 'concurrent/map'
module ActionView
class PartialIteration
@@ -337,7 +337,7 @@ module ActionView
layout = find_template(layout.to_s, @template_keys)
end
- object ||= locals[as]
+ object = locals[as] if object.nil? # Respect object when object is false
locals[as] = object if @has_object
content = @template.render(view, locals) do |*name|
diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb
index 1bee35d80d..2a3b89aebf 100644
--- a/actionview/lib/action_view/renderer/renderer.rb
+++ b/actionview/lib/action_view/renderer/renderer.rb
@@ -15,7 +15,7 @@ module ActionView
@lookup_context = lookup_context
end
- # Main render entry point shared by AV and AC.
+ # Main render entry point shared by Action View and Action Controller.
def render(context, options)
if options.key?(:partial)
render_partial(context, options)
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 0ed208f27e..15fc2b71a3 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -325,7 +325,7 @@ module ActionView
template = refresh(view)
template.encode!
end
- raise Template::Error.new(template, e)
+ raise Template::Error.new(template)
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 390bce98a2..b03b197cb5 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -59,13 +59,20 @@ module ActionView
class Error < ActionViewError #:nodoc:
SOURCE_CODE_RADIUS = 3
- attr_reader :original_exception
+ def initialize(template, original_exception = nil)
+ if original_exception
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
+ super($!.message)
+ set_backtrace($!.backtrace)
+ @template, @sub_templates = template, nil
+ end
- def initialize(template, original_exception)
- super(original_exception.message)
- @template, @original_exception = template, original_exception
- @sub_templates = nil
- set_backtrace(original_exception.backtrace)
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
def file_name
diff --git a/actionview/test/fixtures/test/_klass.erb b/actionview/test/fixtures/test/_klass.erb
new file mode 100644
index 0000000000..9936f86001
--- /dev/null
+++ b/actionview/test/fixtures/test/_klass.erb
@@ -0,0 +1 @@
+<%= klass.class.name %> \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 496b33b35e..a63ac442cd 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -97,6 +97,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" ></script>),
%(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>),
%(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("bank", :host => "assets.example.com")) => %(<script src="http://assets.example.com/javascripts/bank.js"></script>),
%(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>),
%(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>),
@@ -141,6 +142,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />),
%(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />),
%(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />),
+ %(stylesheet_link_tag("bank", :host => "assets.example.com")) => %(<link href="http://assets.example.com/stylesheets/bank.css" media="screen" rel="stylesheet" />),
%(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" />),
%(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" />),
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 9212420ec9..c4234a71c3 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -1559,6 +1559,26 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10))
end
+ def test_date_select_with_selected_in_hash
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :selected => {day: 10, month: 07, year: 2004})
+ end
+
def test_date_select_with_selected_nil
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 41f31f1582..1f7ff3ca7c 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1281,7 +1281,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_week_field
- expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W25" />}
assert_dom_equal(expected, week_field("post", "written_on"))
end
@@ -1292,13 +1292,13 @@ class FormHelperTest < ActionView::TestCase
end
def test_week_field_with_datetime_value
- expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W25" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
assert_dom_equal(expected, week_field("post", "written_on"))
end
def test_week_field_with_extra_attrs
- expected = %{<input id="post_written_on" step="2" max="2010-W51" min="2000-W06" name="post[written_on]" type="week" value="2004-W24" />}
+ expected = %{<input id="post_written_on" step="2" max="2010-W51" min="2000-W06" name="post[written_on]" type="week" value="2004-W25" />}
@post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
min_value = DateTime.new(2000, 2, 13)
max_value = DateTime.new(2010, 12, 23)
@@ -1308,13 +1308,19 @@ class FormHelperTest < ActionView::TestCase
def test_week_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
- expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W25" />}
@post.written_on = Time.zone.parse('2004-06-15 15:30:45')
assert_dom_equal(expected, week_field("post", "written_on"))
ensure
Time.zone = previous_time_zone
end
+ def test_week_field_week_number_base
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2015-W01" />}
+ @post.written_on = DateTime.new(2015, 1, 1, 1, 2, 3)
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ end
+
def test_url_field
expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
@@ -1539,9 +1545,10 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_requires_block
- assert_raises(ArgumentError) do
- form_for(:post, @post, html: { id: 'create-post' })
+ error = assert_raises(ArgumentError) do
+ form_for(@post, html: { id: 'create-post' })
end
+ assert_equal "Missing block", error.message
end
def test_form_for_requires_arguments
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 00fc28a522..84aca222b2 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -247,6 +247,8 @@ module RenderTestCases
def test_render_object
assert_equal "Hello: david", @view.render(:partial => "test/customer", :object => Customer.new("david"))
+ assert_equal "FalseClass", @view.render(:partial => "test/klass", :object => false)
+ assert_equal "NilClass", @view.render(:partial => "test/klass", :object => nil)
end
def test_render_object_with_array
@@ -352,8 +354,8 @@ module RenderTestCases
exception = assert_raises ActionView::Template::Error do
@controller_view.render("partial_name_local_variable")
end
- assert_instance_of NameError, exception.original_exception
- assert_equal :partial_name_local_variable, exception.original_exception.name
+ assert_instance_of NameError, exception.cause
+ assert_equal :partial_name_local_variable, exception.cause.name
end
# TODO: The reason for this test is unclear, improve documentation
@@ -590,14 +592,14 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_incompatible_external_encoding
with_external_encoding Encoding::SHIFT_JIS do
e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") }
- assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
+ assert_match 'Your template was not saved as valid Shift_JIS', e.cause.message
end
end
def test_render_utf8_template_with_partial_with_incompatible_encoding
with_external_encoding Encoding::SHIFT_JIS do
e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") }
- assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
+ assert_match 'Your template was not saved as valid Shift_JIS', e.cause.message
end
end
diff --git a/actionview/test/template/template_error_test.rb b/actionview/test/template/template_error_test.rb
index 3971ec809c..54c1d53b60 100644
--- a/actionview/test/template/template_error_test.rb
+++ b/actionview/test/template/template_error_test.rb
@@ -2,19 +2,34 @@ require "abstract_unit"
class TemplateErrorTest < ActiveSupport::TestCase
def test_provides_original_message
- error = ActionView::Template::Error.new("test", Exception.new("original"))
+ error = begin
+ raise Exception.new("original")
+ rescue Exception
+ raise ActionView::Template::Error.new("test") rescue $!
+ end
+
assert_equal "original", error.message
end
def test_provides_original_backtrace
- original_exception = Exception.new
- original_exception.set_backtrace(%W[ foo bar baz ])
- error = ActionView::Template::Error.new("test", original_exception)
+ error = begin
+ original_exception = Exception.new
+ original_exception.set_backtrace(%W[ foo bar baz ])
+ raise original_exception
+ rescue Exception
+ raise ActionView::Template::Error.new("test") rescue $!
+ end
+
assert_equal %W[ foo bar baz ], error.backtrace
end
def test_provides_useful_inspect
- error = ActionView::Template::Error.new("test", Exception.new("original"))
+ error = begin
+ raise Exception.new("original")
+ rescue Exception
+ raise ActionView::Template::Error.new("test") rescue $!
+ end
+
assert_equal "#<ActionView::Template::Error: original>", error.inspect
end
end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 261576bead..631bceadd8 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -66,6 +66,14 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert translate(:"translations.missing").html_safe?
end
+ def test_returns_missing_translation_message_does_filters_out_i18n_options
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing, year: 2015">Missing</span>'
+ assert_equal expected, translate(:"translations.missing", year: '2015', default: [])
+
+ expected = '<span class="translation_missing" title="translation missing: en.scoped.translations.missing, year: 2015">Missing</span>'
+ assert_equal expected, translate(:"translations.missing", year: '2015', scope: %i(scoped))
+ end
+
def test_raises_missing_translation_message_with_raise_config_option
ActionView::Base.raise_on_missing_translations = true
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 50b7865f88..48d0a9a47a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -50,6 +50,23 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
+ def test_url_for_with_back_and_no_controller
+ @controller = nil
+ assert_equal 'javascript:history.back()', url_for(:back)
+ end
+
+ def test_url_for_with_back_and_javascript_referer
+ referer = 'javascript:alert(document.cookie)'
+ @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer))
+ assert_equal 'javascript:history.back()', url_for(:back)
+ end
+
+ def test_url_for_with_invalid_referer
+ referer = 'THIS IS NOT A URL'
+ @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer))
+ assert_equal 'javascript:history.back()', url_for(:back)
+ end
+
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
@@ -523,6 +540,20 @@ class UrlHelperTest < ActiveSupport::TestCase
mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe)
end
+ def test_mail_to_with_html_safe_string
+ assert_dom_equal(
+ %{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>},
+ mail_to("david@loudthinking.com".html_safe)
+ )
+ end
+
+ def test_mail_to_with_nil
+ assert_dom_equal(
+ %{<a href="mailto:"></a>},
+ mail_to(nil)
+ )
+ end
+
def test_mail_to_returns_html_safe_string
assert mail_to("david@loudthinking.com").html_safe?
end
diff --git a/activejob/README.md b/activejob/README.md
index f9a3183b1a..d9ff561695 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -44,7 +44,7 @@ end
Enqueue a job like so:
```ruby
-MyJob.perform_later record # Enqueue a job to be performed as soon the queueing system is free.
+MyJob.perform_later record # Enqueue a job to be performed as soon as the queueing system is free.
```
```ruby
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 8e462bfe5d..e56bc79328 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -3,16 +3,23 @@ require 'active_support/core_ext/hash'
module ActiveJob
# Raised when an exception is raised during job arguments deserialization.
#
- # Wraps the original exception raised as +original_exception+.
+ # Wraps the original exception raised as +cause+.
class DeserializationError < StandardError
+ def initialize(e = nil) #:nodoc:
+ if e
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
+ super("Error while trying to deserialize arguments: #{$!.message}")
+ set_backtrace $!.backtrace
+ end
+
# The original exception that was raised during deserialization of job
# arguments.
- attr_reader :original_exception
-
- def initialize(e) #:nodoc:
- super("Error while trying to deserialize arguments: #{e.message}")
- @original_exception = e
- set_backtrace e.backtrace
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
@@ -41,8 +48,8 @@ module ActiveJob
# All other types are deserialized using GlobalID.
def deserialize(arguments)
arguments.map { |argument| deserialize_argument(argument) }
- rescue => e
- raise DeserializationError.new(e)
+ rescue
+ raise DeserializationError
end
private
diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb
index 6c1c070994..ed7a6e8d9b 100644
--- a/activejob/lib/active_job/async_job.rb
+++ b/activejob/lib/active_job/async_job.rb
@@ -1,4 +1,7 @@
-require 'concurrent'
+require 'concurrent/map'
+require 'concurrent/scheduled_task'
+require 'concurrent/executor/thread_pool_executor'
+require 'concurrent/utility/processor_counter'
module ActiveJob
# == Active Job Async Job
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index e5f09f65fb..ff5c69ddc6 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -36,7 +36,7 @@ module ActiveJob #:nodoc:
# Records that are passed in are serialized/deserialized using Global
# ID. More information can be found in Arguments.
#
- # To enqueue a job to be performed as soon the queueing system is free:
+ # To enqueue a job to be performed as soon as the queueing system is free:
#
# ProcessPhotoJob.perform_later(photo)
#
diff --git a/activejob/test/adapters/async.rb b/activejob/test/adapters/async.rb
index df58027599..5fcfb89566 100644
--- a/activejob/test/adapters/async.rb
+++ b/activejob/test/adapters/async.rb
@@ -1,4 +1,3 @@
-require 'concurrent'
require 'active_job/async_job'
ActiveJob::Base.queue_adapter = :async
diff --git a/activejob/test/jobs/rescue_job.rb b/activejob/test/jobs/rescue_job.rb
index f1b9c9349e..4f6376c850 100644
--- a/activejob/test/jobs/rescue_job.rb
+++ b/activejob/test/jobs/rescue_job.rb
@@ -11,7 +11,7 @@ class RescueJob < ActiveJob::Base
rescue_from(ActiveJob::DeserializationError) do |e|
JobBuffer.add('rescued from DeserializationError')
- JobBuffer.add("DeserializationError original exception was #{e.original_exception.class.name}")
+ JobBuffer.add("DeserializationError original exception was #{e.cause.class.name}")
end
def perform(person = "david")
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 1963a3fc4e..cc6285f932 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
require 'mutex_m'
module ActiveModel
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 48005eee1a..2461dd517b 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,201 @@
+* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`.
+
+ Fixes #21893.
+
+ *Yuichiro Kaneko*
+
+* Deprecate `connection.tables` on the SQLite3 and MySQL adapters.
+ Also deprecate passing arguments to `#tables`.
+ And deprecate `table_exists?`.
+
+ The `#tables` method of some adapters (mysql, mysql2, sqlite3) would return
+ both tables and views while others (postgresql) just return tables. To make
+ their behavior consistent, `#tables` will return only tables in the future.
+
+ The `#table_exists?` method would check both tables and views. To make
+ their behavior consistent with `#tables`, `#table_exists?` will check only
+ tables in the future.
+
+ *Yuichiro Kaneko*
+
+* Improve support for non Active Record objects on `validates_associated`
+
+ Skipping `marked_for_destruction?` when the associated object does not responds
+ to it make easier to validate virtual associations built on top of Active Model
+ objects and/or serialized objects that implement a `valid?` instance method.
+
+ *Kassio Borges*, *Lucas Mazza*
+
+* Change connection management middleware to return a new response with
+ a body proxy, rather than mutating the original.
+
+ *Kevin Buchanan*
+
+* Make `db:migrate:status` to render `1_some.rb` format migrate files.
+
+ These files are in `db/migrate`:
+
+ * 1_valid_people_have_last_names.rb
+ * 20150819202140_irreversible_migration.rb
+ * 20150823202140_add_admin_flag_to_users.rb
+ * 20150823202141_migration_tests.rb
+ * 2_we_need_reminders.rb
+ * 3_innocent_jointable.rb
+
+ Before:
+
+ $ bundle exec rake db:migrate:status
+ ...
+
+ Status Migration ID Migration Name
+ --------------------------------------------------
+ up 001 ********** NO FILE **********
+ up 002 ********** NO FILE **********
+ up 003 ********** NO FILE **********
+ up 20150819202140 Irreversible migration
+ up 20150823202140 Add admin flag to users
+ up 20150823202141 Migration tests
+
+ After:
+
+ $ bundle exec rake db:migrate:status
+ ...
+
+ Status Migration ID Migration Name
+ --------------------------------------------------
+ up 001 Valid people have last names
+ up 002 We need reminders
+ up 003 Innocent jointable
+ up 20150819202140 Irreversible migration
+ up 20150823202140 Add admin flag to users
+ up 20150823202141 Migration tests
+
+ *Yuichiro Kaneko*
+
+* Define `ActiveRecord::Sanitization.sanitize_sql_for_order` and use it inside
+ `preprocess_order_args`.
+
+ *Yuichiro Kaneko*
+
+* Allow bigint with default nil for avoiding auto increment primary key.
+
+ *Ryuta Kamizono*
+
+* Remove `DEFAULT_CHARSET` and `DEFAULT_COLLATION` in `MySQLDatabaseTasks`.
+
+ We should omit the collation entirely rather than providing a default.
+ Then the choice is the responsibility of the server and MySQL distribution.
+
+ *Ryuta Kamizono*
+
+* Alias `ActiveRecord::Relation#left_joins` to
+ `ActiveRecord::Relation#left_outer_joins`.
+
+ *Takashi Kokubun*
+
+* Use advisory locking to raise a `ConcurrentMigrationError` instead of
+ attempting to migrate when another migration is currently running.
+
+ *Sam Davies*
+
+* Added `ActiveRecord::Relation#left_outer_joins`.
+
+ Example:
+
+ User.left_outer_joins(:posts)
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON
+ "posts"."user_id" = "users"."id"
+
+ *Florian Thomas*
+
+* Support passing an array to `order` for SQL parameter sanitization.
+
+ *Aaron Suggs*
+
+* Avoid disabling errors on the PostgreSQL connection when enabling the
+ `standard_conforming_strings` setting. Errors were previously disabled because
+ the setting wasn't writable in Postgres 8.1 and didn't exist in earlier
+ versions. Now Rails only supports Postgres 8.2+ we're fine to assume the
+ setting exists. Disabling errors caused problems when using a connection
+ pooling tool like PgBouncer because it's not guaranteed to have the same
+ connection between calls to `execute` and it could leave the connection
+ with errors disabled.
+
+ Fixes #22101.
+
+ *Harry Marr*
+
+* Set `scope.reordering_value` to `true` if `:reordering`-values are specified.
+
+ Fixes #21886.
+
+ *Hiroaki Izu*
+
+* Add support for bidirectional destroy dependencies.
+
+ Fixes #13609.
+
+ Example:
+
+ class Content < ActiveRecord::Base
+ has_one :position, dependent: :destroy
+ end
+
+ class Position < ActiveRecord::Base
+ belongs_to :content, dependent: :destroy
+ end
+
+ *Seb Jacobs*
+
+* Includes HABTM returns correct size now. It's caused by the join dependency
+ only instantiates one HABTM object because the join table hasn't a primary key.
+
+ Fixes #16032.
+
+ Examples:
+
+ before:
+
+ Project.first.salaried_developers.size # => 3
+ Project.includes(:salaried_developers).first.salaried_developers.size # => 1
+
+ after:
+
+ Project.first.salaried_developers.size # => 3
+ Project.includes(:salaried_developers).first.salaried_developers.size # => 3
+
+ *Bigxiang*
+
+* Add option to index errors in nested attributes
+
+ For models which have nested attributes, errors within those models will
+ now be indexed if :index_errors is specified when defining a
+ has_many relationship, or if its set in the global config.
+
+ Example:
+
+ class Guitar < ActiveRecord::Base
+ has_many :tuning_pegs
+ accepts_nested_attributes_for :tuning_pegs
+ end
+
+ class TuningPeg < ActiveRecord::Base
+ belongs_to :guitar
+ validates_numericality_of :pitch
+ end
+
+ # Old style
+ guitar.errors["tuning_pegs.pitch"] = ["is not a number"]
+
+ # New style (if defined globally, or set in has_many_relationship)
+ guitar.errors["tuning_pegs[1].pitch"] = ["is not a number"]
+
+ *Michael Probber*, *Terence Sun*
+
+* Exit with non-zero status for failed database rake tasks.
+
+ *Jay Hayes*
+
* Queries such as `Computer.joins(:monitor).group(:status).count` will now be
interpreted as `Computer.joins(:monitor).group('computers.status').count`
so that when `Computer` and `Monitor` have both `status` columns we don't
@@ -5,21 +203,23 @@
*Rafael Sales*
-* Add ability to default to `uuid` as primary key when generating database migrations
+* Add ability to default to `uuid` as primary key when generating database migrations.
+
+ Example:
- config.generators do |g|
- g.orm :active_record, primary_key_type: :uuid
- end
+ config.generators do |g|
+ g.orm :active_record, primary_key_type: :uuid
+ end
*Jon McCartie*
-* Don't cache arguments in #find_by if they are an ActiveRecord::Relation
+* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`.
Fixes #20817
*Hiroaki Izu*
-* Qualify column name inserted by `group` in calculation
+* Qualify column name inserted by `group` in calculation.
Giving `group` an unqualified column name now works, even if the relation
has `JOIN` with another table which also has a column of the name.
@@ -44,7 +244,7 @@
*Jake Worth*
* Add an immutable string type to help reduce memory usage for apps which do
- not need mutation detection on Strings.
+ not need mutation detection on strings.
*Sean Griffin*
@@ -62,7 +262,7 @@
*Yves Senn*
-* No longer pass depreacted option `-i` to `pg_dump`.
+* No longer pass deprecated option `-i` to `pg_dump`.
*Paul Sadauskas*
@@ -77,7 +277,7 @@
*Matthew Draper*, *Jean Boussier*
-* Remove unused `pk_and_sequence_for` in AbstractMysqlAdapter.
+* Remove unused `pk_and_sequence_for` in `AbstractMysqlAdapter`.
*Ryuta Kamizono*
@@ -103,16 +303,15 @@
*Jimmy Bourassa*
-* Fixed taking precision into count when assigning a value to timestamp attribute
+* Fixed taking precision into count when assigning a value to timestamp attribute.
Timestamp column can have less precision than ruby timestamp
In result in how big a fraction of a second can be stored in the
database.
- m = Model.create!
- m.created_at.usec == m.reload.created_at.usec
- # => false
+ m = Model.create!
+ m.created_at.usec == m.reload.created_at.usec # => false
# due to different precision in Time.now and database column
If the precision is low enough, (mysql default is 0, so it is always low
@@ -134,7 +333,7 @@
*Yves Senn*, *Matthew Draper*
* Add `ActiveRecord::Base.ignored_columns` to make some columns
- invisible from ActiveRecord.
+ invisible from Active Record.
*Jean Boussier*
@@ -300,9 +499,9 @@
Example:
- @users = User.where("name like ?", "%Alberto%")
- @users.cache_key
- => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
+ @users = User.where("name like ?", "%Alberto%")
+ @users.cache_key
+ # => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
*Alberto Fernández-Capel*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d4f079e346..b806a2f832 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -149,7 +149,7 @@ module ActiveRecord
class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
end
- # This error is raised when trying to eager load a poloymorphic association using a JOIN.
+ # This error is raised when trying to eager load a polymorphic association using a JOIN.
# Eager loading polymorphic associations is only possible with
# {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
class EagerLoadPolymorphicError < ActiveRecordError
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index c7b396f3d4..d64ab64c99 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -163,9 +163,12 @@ module ActiveRecord
@reflection = @owner.class._reflect_on_association(reflection_name)
end
- def initialize_attributes(record) #:nodoc:
+ def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
+ except_from_scope_attributes ||= {}
skip_assign = [reflection.foreign_key, reflection.type].compact
- attributes = create_scope.except(*(record.changed - skip_assign))
+ assigned_keys = record.changed
+ assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
+ attributes = create_scope.except(*(assigned_keys - skip_assign))
record.assign_attributes(attributes)
set_inverse_instance(record)
end
@@ -248,7 +251,7 @@ module ActiveRecord
def build_record(attributes)
reflection.build_association(attributes) do |record|
- initialize_attributes(record)
+ initialize_attributes(record, attributes)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index dae468ba54..81c535d962 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -116,8 +116,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.add_destroy_callbacks(model, reflection)
- name = reflection.name
- model.after_destroy lambda { |o| o.association(name).handle_dependency }
+ model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
end
def self.define_validations(model, reflection)
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index a5c9f1666e..b888148841 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -62,13 +62,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.add_left_association(name, options)
- belongs_to name, options
+ belongs_to name, required: false, **options
self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
- belongs_to rhs_name, options
+ belongs_to rhs_name, required: false, **options
self.right_reflection = _reflect_on_association(rhs_name)
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index f71b10120f..7864d4c536 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index ca473beea3..0e4e951269 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -32,7 +32,7 @@ module ActiveRecord
@alias_cache[node][column]
end
- class Table < Struct.new(:node, :columns)
+ class Table < Struct.new(:node, :columns) # :nodoc:
def table
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
end
@@ -103,9 +103,14 @@ module ActiveRecord
join_root.drop(1).map!(&:reflection)
end
- def join_constraints(outer_joins)
+ def join_constraints(outer_joins, join_type)
joins = join_root.children.flat_map { |child|
- make_inner_joins join_root, child
+
+ if join_type == Arel::Nodes::OuterJoin
+ make_left_outer_joins join_root, child
+ else
+ make_inner_joins join_root, child
+ end
}
joins.concat outer_joins.flat_map { |oj|
@@ -150,7 +155,8 @@ module ActiveRecord
message_bus.instrument('instantiation.active_record', payload) do
result_set.each { |row_hash|
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
}
end
@@ -175,6 +181,14 @@ module ActiveRecord
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
end
+ def make_left_outer_joins(parent, child)
+ tables = child.tables
+ join_type = Arel::Nodes::OuterJoin
+ info = make_constraints parent, child, tables, join_type
+
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
+ end
+
def make_inner_joins(parent, child)
tables = child.tables
join_type = Arel::Nodes::InnerJoin
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 3992a240b9..ecf6fb8643 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -54,6 +54,8 @@ module ActiveRecord
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
end
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
+
# Eager loads the named associations for the given Active Record record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -88,9 +90,6 @@ module ActiveRecord
# [ :books, :author ]
# { author: :avatar }
# [ :books, { author: :avatar } ]
-
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
-
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
associations = Array.wrap(associations)
@@ -107,6 +106,7 @@ module ActiveRecord
private
+ # Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope)
case association
when Hash
@@ -132,6 +132,11 @@ module ActiveRecord
}
end
+ # Loads all the given data into +records+ for a singular +association+.
+ #
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # call the +run+ method for each passed in class in the +records+ argument.
+ #
# Not all records have the same class, so group then preload group on the reflection
# itself so that if various subclass share the same association then we do not split
# them unnecessarily
@@ -181,6 +186,10 @@ module ActiveRecord
def self.preloaded_records; []; end
end
+ # Returns a class containing the logic needed to load preload the data
+ # and attach it to a relation. For example +Preloader::Association+ or
+ # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # that accepts a preloader.
def preloader_for(reflection, owners, rhs_klass)
return NullPreloader unless rhs_klass
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 29dd0643d6..e11a5cfb8a 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -134,7 +134,14 @@ module ActiveRecord
else
scope.joins!(reflection_scope.joins_values)
end
- scope.order! preload_values[:order] || values[:order]
+
+ if order_values = preload_values[:order] || values[:order]
+ scope.order!(order_values)
+ end
+
+ if preload_values[:reordering] || values[:reordering]
+ scope.reordering_value = true
+ end
if preload_values[:readonly] || values[:readonly]
scope.readonly!
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index 5adffcd831..9939280fa4 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -2,13 +2,8 @@ module ActiveRecord
module Associations
class Preloader
class CollectionAssociation < Association #:nodoc:
-
private
- def build_scope
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
- end
-
def preload(preloader)
associated_records_by_owner(preloader).each do |owner, records|
association = owner.association(reflection.name)
@@ -17,7 +12,6 @@ module ActiveRecord
records.each { |record| association.set_inverse_instance(record) }
end
end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
index 24728e9f01..c4add621ca 100644
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_one.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Associations
class Preloader
class HasOne < SingularAssociation #:nodoc:
-
def association_key_name
reflection.foreign_key
end
@@ -10,13 +9,6 @@ module ActiveRecord
def owner_key_name
reflection.active_record_primary_key
end
-
- private
-
- def build_scope
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
- end
-
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 56aa23b173..24aa47bb51 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -84,7 +84,9 @@ module ActiveRecord
end
scope.references! reflection_scope.values[:references]
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
+ scope = scope.order(order_values)
+ end
end
scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index cbdd4950a6..4ae585d3f5 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/filters'
require 'mutex_m'
-require 'concurrent'
+require 'concurrent/map'
module ActiveRecord
# = Active Record Attribute Methods
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index d35bc3e794..fc12c3f45a 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -140,6 +140,8 @@ module ActiveRecord
included do
Associations::Builder::Association.extensions << AssociationBuilderExtension
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
+ self.index_nested_attribute_errors = false
end
module ClassMethods # :nodoc:
@@ -315,7 +317,7 @@ module ActiveRecord
def validate_collection_association(reflection)
if association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
- records.each { |record| association_valid?(reflection, record) }
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
end
end
end
@@ -323,14 +325,18 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, record)
+ def association_valid?(reflection, record, index=nil)
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
- attribute = "#{reflection.name}.#{attribute}"
+ if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
+ attribute = "#{reflection.name}.#{attribute}"
+ else
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
+ end
errors[attribute] << message
errors[attribute].uniq!
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index bfedc4af3f..cfd8cbda67 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -292,10 +292,15 @@ module ActiveRecord
end
def destroy #:nodoc:
+ @_destroy_callback_already_called ||= false
+ return if @_destroy_callback_already_called
+ @_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
@_association_destroy_exception = e
false
+ ensure
+ @_destroy_callback_already_called = false
end
def touch(*) #:nodoc:
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 0d850c7625..486b7b6d25 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,5 +1,5 @@
require 'thread'
-require 'concurrent'
+require 'concurrent/map'
require 'monitor'
module ActiveRecord
@@ -960,12 +960,11 @@ module ActiveRecord
def call(env)
testing = env['rack.test']
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) do
+ status, headers, body = @app.call(env)
+ proxy = ::Rack::BodyProxy.new(body) do
ActiveRecord::Base.clear_active_connections! unless testing
end
-
- response
+ [status, headers, proxy]
rescue Exception
ActiveRecord::Base.clear_active_connections! unless testing
raise
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index e2ef56798b..abf0124562 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -202,13 +202,9 @@ module ActiveRecord
# end
# end
#
- # The table definitions
- # The Columns are stored as a ColumnDefinition in the #columns attribute.
class TableDefinition
include ColumnMethods
- # An array of ColumnDefinition objects, representing the column changes
- # that have been defined.
attr_accessor :indexes
attr_reader :name, :temporary, :options, :as, :foreign_keys, :native
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 d5f8dbc8fc..5cacf6eddc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -263,7 +263,7 @@ module ActiveRecord
yield td if block_given?
- if options[:force] && table_exists?(table_name)
+ if options[:force] && data_source_exists?(table_name)
drop_table(table_name, options)
end
@@ -1088,7 +1088,7 @@ module ActiveRecord
if index_name.length > max_index_length
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
- if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 402159ac13..4d4dc07b04 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -214,6 +214,11 @@ module ActiveRecord
false
end
+ # Does this adapter support application-enforced advisory locking?
+ def supports_advisory_locks?
+ false
+ end
+
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
@@ -280,6 +285,20 @@ module ActiveRecord
def enable_extension(name)
end
+ # This is meant to be implemented by the adapters that support advisory
+ # locks
+ #
+ # Return true if we got the lock, otherwise false
+ def get_advisory_lock(lock_id) # :nodoc:
+ end
+
+ # This is meant to be implemented by the adapters that support advisory
+ # locks.
+ #
+ # Return true if we released the lock, otherwise false
+ def release_advisory_lock(lock_id) # :nodoc:
+ end
+
# A list of extensions, to be filled in by adapters that support them.
def extensions
[]
@@ -519,7 +538,7 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message, exception)
+ ActiveRecord::StatementInvalid.new(message)
end
def without_prepared_statement?(binds)
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 251acf1c83..deef246c37 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -134,9 +134,7 @@ module ActiveRecord
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob" },
- blob: { name: "blob" },
boolean: { name: "tinyint", limit: 1 },
- bigint: { name: "bigint" },
json: { name: "json" },
}
@@ -220,6 +218,20 @@ module ActiveRecord
version >= '5.6.4'
end
+ # 5.0.0 definitely supports it, possibly supported by earlier versions but
+ # not sure
+ def supports_advisory_locks?
+ version >= '5.0.0'
+ end
+
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
+ end
+
+ def release_advisory_lock(lock_name) # :nodoc:
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -483,18 +495,43 @@ module ActiveRecord
end
def tables(name = nil) # :nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #tables currently returns both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
+ Use #data_sources instead.
+ MSG
+
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
+ data_sources
+ end
+
+ def data_sources
sql = "SELECT table_name FROM information_schema.tables "
sql << "WHERE table_schema = #{quote(@config[:database])}"
select_values(sql, 'SCHEMA')
end
- alias data_sources tables
def truncate(table_name, name = nil)
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
def table_exists?(table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(table_name)
+ end
+
+ def data_source_exists?(table_name)
return false unless table_name.present?
schema, name = table_name.to_s.split('.', 2)
@@ -505,7 +542,6 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
@@ -853,9 +889,9 @@ module ActiveRecord
def translate_exception(exception, message)
case error_number(exception)
when 1062
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
when 1452
- InvalidForeignKey.new(message, exception)
+ InvalidForeignKey.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index 29e8c73d46..ca7dfda80d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module MySQL
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if type == :bigint
+ options[:auto_increment] = true if type == :bigint && !options.key?(:default)
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 3c48d0554e..9dee3172f4 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -4,8 +4,11 @@ module ActiveRecord
module ColumnDumper
def column_spec_for_primary_key(column)
spec = {}
- if column.auto_increment?
- spec[:id] = ':bigint' if column.bigint?
+ if column.bigint?
+ spec[:id] = ':bigint'
+ spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
+ spec[:unsigned] = 'true' if column.unsigned?
+ elsif column.auto_increment?
spec[:unsigned] = 'true' if column.unsigned?
return if spec.empty?
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 42c4a14f00..773ecbe126 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -20,7 +20,7 @@ module ActiveRecord
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
rescue Mysql2::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index fddb318553..89d18ee14e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -38,7 +38,7 @@ module ActiveRecord
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
rescue Mysql::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
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 aaf5b2898b..a48d64f7bd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -70,6 +70,12 @@ module ActiveRecord
# Returns the list of all tables in the schema search path.
def tables(name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
@@ -87,6 +93,16 @@ module ActiveRecord
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(name)
+ end
+
+ def data_source_exists?(name)
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
@@ -99,7 +115,6 @@ module ActiveRecord
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values(<<-SQL, 'SCHEMA')
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 236c067fd5..719592349b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -19,12 +19,6 @@ require 'ipaddr'
module ActiveRecord
module ConnectionHandling # :nodoc:
- VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
- :client_encoding, :options, :application_name, :fallback_application_name,
- :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
- :tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
- :sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
-
# Establishes a connection to the database that's used by all Active Record objects
def postgresql_connection(config)
conn_params = config.symbolize_keys
@@ -36,7 +30,8 @@ module ActiveRecord
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
# Forward only valid config params to PGconn.connect.
- conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
+ valid_conn_param_keys = PGconn.conndefaults_hash.keys + [:requiressl]
+ conn_params.slice!(*valid_conn_param_keys)
# The postgres drivers don't allow the creation of an unconnected PGconn object,
# so just pass a nil connection object for the time being.
@@ -77,7 +72,6 @@ module ActiveRecord
NATIVE_DATABASE_TYPES = {
primary_key: "serial primary key",
- bigserial: "bigserial",
string: { name: "character varying" },
text: { name: "text" },
integer: { name: "integer" },
@@ -94,7 +88,6 @@ module ActiveRecord
int8range: { name: "int8range" },
binary: { name: "bytea" },
boolean: { name: "boolean" },
- bigint: { name: "bigint" },
xml: { name: "xml" },
tsvector: { name: "tsvector" },
hstore: { name: "hstore" },
@@ -281,18 +274,18 @@ module ActiveRecord
true
end
- # Enable standard-conforming strings if available.
def set_standard_conforming_strings
- old, self.client_min_messages = client_min_messages, 'panic'
- execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
- ensure
- self.client_min_messages = old
+ execute('SET standard_conforming_strings = on', 'SCHEMA')
end
def supports_ddl_transactions?
true
end
+ def supports_advisory_locks?
+ true
+ end
+
def supports_explain?
true
end
@@ -311,6 +304,20 @@ module ActiveRecord
postgresql_version >= 90300
end
+ def get_advisory_lock(lock_id) # :nodoc:
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ end
+ select_value("SELECT pg_try_advisory_lock(#{lock_id});")
+ end
+
+ def release_advisory_lock(lock_id) # :nodoc:
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ end
+ select_value("SELECT pg_advisory_unlock(#{lock_id})")
+ end
+
def enable_extension(name)
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
reload_type_map
@@ -393,9 +400,9 @@ module ActiveRecord
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
when UNIQUE_VIOLATION
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
- InvalidForeignKey.new(message, exception)
+ InvalidForeignKey.new(message)
else
super
end
@@ -580,7 +587,7 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.original_exception
+ pgerror = e.cause
# Get the PG code for the failure. Annoyingly, the code for
# prepared statements whose return value may have changed is
@@ -636,7 +643,7 @@ module ActiveRecord
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
@@ -651,7 +658,7 @@ module ActiveRecord
self.client_min_messages = @config[:min_messages] || 'warning'
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
- # Use standard-conforming strings if available so we don't have to do the E'...' dance.
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
set_standard_conforming_strings
# If using Active Record's time zone support configure the connection to return
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 9028c1fcb9..90df9b8825 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -33,7 +33,7 @@ module ActiveRecord
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
+ raise ActiveRecord::NoDatabaseError
else
raise
end
@@ -312,11 +312,36 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
def tables(name = nil) # :nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #tables currently returns both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
+ Use #data_sources instead.
+ MSG
+
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing arguments to #tables is deprecated without replacement.
+ MSG
+ end
+
+ data_sources
+ end
+
+ def data_sources
select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
end
- alias data_sources tables
def table_exists?(table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #table_exists? currently checks both tables and views.
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
+ Use #data_source_exists? instead.
+ MSG
+
+ data_source_exists?(table_name)
+ end
+
+ def data_source_exists?(table_name)
return false unless table_name.present?
sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
@@ -324,7 +349,6 @@ module ActiveRecord
select_values(sql, 'SCHEMA').any?
end
- alias data_source_exists? table_exists?
def views # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
@@ -559,7 +583,7 @@ module ActiveRecord
# Older versions of SQLite return:
# column *column_name* is not unique
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
- RecordNotUnique.new(message, exception)
+ RecordNotUnique.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 142b6e8599..1250f8a3c3 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -193,8 +193,8 @@ module ActiveRecord
}
begin
statement.execute(hash.values, self, connection).first
- rescue TypeError => e
- raise ActiveRecord::StatementInvalid.new(e.message, e)
+ rescue TypeError
+ raise ActiveRecord::StatementInvalid
rescue RangeError
nil
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 8fba6fcc35..7ded96f8fb 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -104,7 +104,7 @@ module ActiveRecord
super
end
- class EnumType < Type::Value
+ class EnumType < Type::Value # :nodoc:
def initialize(name, mapping)
@name = name
@mapping = mapping
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 533c86a6a9..1cd2c2ef8c 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -94,13 +94,21 @@ module ActiveRecord
# Superclass for all database execution errors.
#
- # Wraps the underlying database error as +original_exception+.
+ # Wraps the underlying database error as +cause+.
class StatementInvalid < ActiveRecordError
- attr_reader :original_exception
def initialize(message = nil, original_exception = nil)
- @original_exception = original_exception
- super(message)
+ if original_exception
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
+ "Exceptions will automatically capture the original exception.", caller)
+ end
+
+ super(message || $!.try(:message))
+ end
+
+ def original_exception
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
+ cause
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 17e7c828b9..deb7d7d06d 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -875,9 +875,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.config = ActiveRecord::Base
- self.fixture_class_names = Hash.new do |h, fixture_set_name|
- h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config)
- end
+ self.fixture_class_names = {}
silence_warnings do
define_singleton_method :use_transactional_tests do
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index c26842014d..589c70db0d 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -55,7 +55,7 @@ module ActiveRecord
subclass = subclass_from_attributes(attrs)
end
- if subclass
+ if subclass && subclass != self
subclass.new(*args, &block)
else
super
@@ -167,17 +167,23 @@ module ActiveRecord
end
def find_sti_class(type_name)
- if store_full_sti_class
- ActiveSupport::Dependencies.constantize(type_name)
- else
- compute_type(type_name)
+ subclass = begin
+ if store_full_sti_class
+ ActiveSupport::Dependencies.constantize(type_name)
+ else
+ compute_type(type_name)
+ end
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
+ "or overwrite #{name}.inheritance_column to use another column for that information."
+ end
+ unless subclass == self || descendants.include?(subclass)
+ raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
end
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{name}.inheritance_column to use another column for that information."
+ subclass
end
def type_condition(table = arel_table)
@@ -199,15 +205,7 @@ module ActiveRecord
subclass_name = attrs.with_indifferent_access[inheritance_column]
if subclass_name.present?
- subclass = find_sti_class(subclass_name)
-
- if subclass.name != self.name
- unless descendants.include?(subclass)
- raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}")
- end
-
- subclass
- end
+ find_sti_class(subclass_name)
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c8b96b8de0..ca2537cdc3 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -135,6 +135,14 @@ module ActiveRecord
end
end
+ class ConcurrentMigrationError < MigrationError #:nodoc:
+ DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze
+
+ def initialize(message = DEFAULT_MESSAGE)
+ super
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -469,6 +477,7 @@ module ActiveRecord
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
+ MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
# loading a web page if config.active_record.migration_error is set to :page_load
@@ -958,10 +967,12 @@ module ActiveRecord
end
def get_all_versions(connection = Base.connection)
- if connection.table_exists?(schema_migrations_table_name)
- SchemaMigration.all.map { |x| x.version.to_i }.sort
- else
- []
+ ActiveSupport::Deprecation.silence do
+ if connection.table_exists?(schema_migrations_table_name)
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
+ else
+ []
+ end
end
end
@@ -987,14 +998,21 @@ module ActiveRecord
Array(@migrations_paths)
end
+ def match_to_migration_filename?(filename) # :nodoc:
+ File.basename(filename) =~ Migration::MigrationFilenameRegexp
+ end
+
+ def parse_migration_filename(filename) # :nodoc:
+ File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ end
+
def migrations(paths)
paths = Array(paths)
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
-
+ version, name, scope = parse_migration_filename(file)
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
name = name.camelize
@@ -1042,32 +1060,18 @@ module ActiveRecord
alias :current :current_migration
def run
- migration = migrations.detect { |m| m.version == @target_version }
- raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
- raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
- end
+ if use_advisory_lock?
+ with_advisory_lock { run_without_lock }
+ else
+ run_without_lock
end
end
def migrate
- if !target && @target_version && @target_version > 0
- raise UnknownMigrationVersionError.new(@target_version)
- end
-
- runnable.each do |migration|
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? "this and " : ""
- raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
- end
+ if use_advisory_lock?
+ with_advisory_lock { migrate_without_lock }
+ else
+ migrate_without_lock
end
end
@@ -1092,10 +1096,45 @@ module ActiveRecord
end
def migrated
- @migrated_versions ||= Set.new(self.class.get_all_versions)
+ @migrated_versions || load_migrated
+ end
+
+ def load_migrated
+ @migrated_versions = Set.new(self.class.get_all_versions)
end
private
+
+ def run_without_lock
+ migration = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
+ end
+ end
+ end
+
+ def migrate_without_lock
+ if !target && @target_version && @target_version > 0
+ raise UnknownMigrationVersionError.new(@target_version)
+ end
+
+ runnable.each do |migration|
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
+
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? "this and " : ""
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
+ end
+ end
+ end
+
def ran?(migration)
migrated.include?(migration.version.to_i)
end
@@ -1157,5 +1196,25 @@ module ActiveRecord
def use_transaction?(migration)
!migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
end
+
+ def use_advisory_lock?
+ Base.connection.supports_advisory_locks?
+ end
+
+ def with_advisory_lock
+ lock_id = generate_migrator_advisory_lock_id
+ got_lock = Base.connection.get_advisory_lock(lock_id)
+ raise ConcurrentMigrationError unless got_lock
+ load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
+ yield
+ ensure
+ Base.connection.release_advisory_lock(lock_id) if got_lock
+ end
+
+ MIGRATOR_SALT = 2053462845
+ def generate_migrator_advisory_lock_id
+ db_name_hash = Zlib.crc32(Base.connection.current_database)
+ MIGRATOR_SALT * db_name_hash
+ end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index a9bd094a66..e3f304b0af 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -339,6 +339,9 @@ module ActiveRecord
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ direct_descendants.each do |descendant|
+ descendant.send(:reload_schema_from_cache)
+ end
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 87a1988f2f..1f429cfd94 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -7,7 +7,7 @@ module ActiveRecord
delegate :find_by, :find_by!, to: :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
delegate :find_each, :find_in_batches, :in_batches, to: :all
- delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or,
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d940ac244a..5b1ea16f0b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -34,7 +34,7 @@ db_namespace = namespace :db do
end
end
- # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
+ # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
task :purge => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
end
@@ -100,12 +100,14 @@ db_namespace = namespace :db do
file_list =
ActiveRecord::Tasks::DatabaseTasks.migrations_paths.flat_map do |path|
- # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
- Dir.foreach(path).grep(/^(\d{3,})_(.+)\.rb$/) do
- version = ActiveRecord::SchemaMigration.normalize_migration_number($1)
+ Dir.foreach(path).map do |file|
+ next unless ActiveRecord::Migrator.match_to_migration_filename?(file)
+
+ version, name, scope = ActiveRecord::Migrator.parse_migration_filename(file)
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
status = db_list.delete(version) ? 'up' : 'down'
- [status, version, $2.humanize]
- end
+ [status, version, (name + scope).humanize]
+ end.compact
end
db_list.map! do |version|
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5b9d45d871..a549b28f16 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -371,7 +371,7 @@ module ActiveRecord
end
def foreign_key
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
end
def association_foreign_key
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 392b462aa9..f100476374 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -4,7 +4,7 @@ module ActiveRecord
# = Active Record \Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :references,
+ :order, :joins, :left_joins, :left_outer_joins, :references,
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index f5afc1000d..2dc52982c9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -428,6 +428,27 @@ module ActiveRecord
self
end
+ # Performs a left outer joins on +args+:
+ #
+ # User.left_outer_joins(:posts)
+ # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
+ #
+ def left_outer_joins(*args)
+ check_if_method_has_arguments!(:left_outer_joins, args)
+
+ args.compact!
+ args.flatten!
+
+ spawn.left_outer_joins!(*args)
+ end
+ alias :left_joins :left_outer_joins
+
+ def left_outer_joins!(*args) # :nodoc:
+ self.left_outer_joins_values += args
+ self
+ end
+ alias :left_joins! :left_outer_joins!
+
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
#
@@ -878,6 +899,7 @@ module ActiveRecord
arel = Arel::SelectManager.new(table)
build_joins(arel, joins_values.flatten) unless joins_values.empty?
+ build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
@@ -937,6 +959,19 @@ module ActiveRecord
end
end
+ def build_left_outer_joins(manager, outer_joins)
+ buckets = outer_joins.group_by do |join|
+ case join
+ when Hash, Symbol, Array
+ :association_join
+ else
+ raise ArgumentError, 'only Hash, Symbol and Array are allowed'
+ end
+ end
+
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
@@ -952,6 +987,11 @@ module ActiveRecord
raise 'unknown class: %s' % join.class.name
end
end
+
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin)
+ end
+
+ def build_join_query(manager, buckets, join_type)
buckets.default = []
association_joins = buckets[:association_join]
@@ -967,7 +1007,7 @@ module ActiveRecord
join_list
)
- join_infos = join_dependency.join_constraints stashed_association_joins
+ join_infos = join_dependency.join_constraints stashed_association_joins, join_type
join_infos.each do |info|
info.joins.each { |join| manager.from(join) }
@@ -1045,6 +1085,9 @@ module ActiveRecord
end
def preprocess_order_args(order_args)
+ order_args.map! do |arg|
+ klass.send(:sanitize_sql_for_order, arg)
+ end
order_args.flatten!
validate_order_args(order_args)
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
index 14e1bf89fa..0a1814b3dd 100644
--- a/activerecord/lib/active_record/relation/record_fetch_warning.rb
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -23,11 +23,13 @@ module ActiveRecord
end
end
+ # :stopdoc:
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
payload = args.last
QueryRegistry.queries << payload[:sql]
end
+ # :startdoc:
class QueryRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 5c3318651a..67d7f83cb4 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -12,6 +12,7 @@ module ActiveRecord
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
+ #
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# # Performs a single join query with both where conditions.
#
@@ -37,11 +38,14 @@ module ActiveRecord
end
def merge!(other) # :nodoc:
- if !other.is_a?(Relation) && other.respond_to?(:to_proc)
+ if other.is_a?(Hash)
+ Relation::HashMerger.new(self, other).merge
+ elsif other.is_a?(Relation)
+ Relation::Merger.new(self, other).merge
+ elsif other.respond_to?(:to_proc)
instance_exec(&other)
else
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
- klass.new(self, other).merge
+ raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 1cf4b09bf3..0c15f45db9 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -53,6 +53,22 @@ module ActiveRecord
end
end
+ # Accepts an array, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a ORDER clause.
+ #
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
+ # # => "field(id, 1,3,2)"
+ #
+ # sanitize_sql_for_order("id ASC")
+ # # => "id ASC"
+ def sanitize_sql_for_order(condition)
+ if condition.is_a?(Array) && condition.first.to_s.include?('?')
+ sanitize_sql_array(condition)
+ else
+ condition
+ end
+ end
+
# Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
# relationship with their expanded aggregate attribute values.
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index b384529e75..51b9b17395 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
end
def table_exists?
- connection.table_exists?(table_name)
+ ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
def create_table(limit=nil)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index ea7927a435..c0c29a618c 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -94,8 +94,9 @@ module ActiveRecord
rescue DatabaseAlreadyExists
$stderr.puts "#{configuration['database']} already exists"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
+ raise
end
def create_all
@@ -115,8 +116,9 @@ module ActiveRecord
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't drop #{configuration['database']}"
+ raise
end
def drop_all
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 8929aa85c8..100df9c6c6 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -1,8 +1,6 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
- DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
- DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
ACCESS_DENIED_ERROR = 1045
delegate :connection, :establish_connection, to: ActiveRecord::Base
@@ -87,12 +85,6 @@ module ActiveRecord
Hash.new.tap do |options|
options[:charset] = configuration['encoding'] if configuration.include? 'encoding'
options[:collation] = configuration['collation'] if configuration.include? 'collation'
-
- # Set default charset only when collation isn't set.
- options[:charset] ||= DEFAULT_CHARSET unless options[:collation]
-
- # Set default collation only when charset is also default.
- options[:collation] ||= DEFAULT_COLLATION if options[:charset] == DEFAULT_CHARSET
end
end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 9ab64d0325..9ec3c8a94a 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -19,11 +19,15 @@ module ActiveRecord
path = Pathname.new configuration['database']
file = path.absolute? ? path.to_s : File.join(root, path)
- FileUtils.rm(file) if File.exist?(file)
+ FileUtils.rm(file)
+ rescue Errno::ENOENT => error
+ raise NoDatabaseError.new(error.message, error)
end
def purge
drop
+ rescue NoDatabaseError
+ ensure
create
end
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 81d7ed39bb..850a7a4e09 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
module ActiveRecord
module Type
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
index 63ba10c289..accc339d00 100644
--- a/activerecord/lib/active_record/type_caster.rb
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -2,6 +2,6 @@ require 'active_record/type_caster/map'
require 'active_record/type_caster/connection'
module ActiveRecord
- module TypeCaster
+ module TypeCaster # :nodoc:
end
end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 868d08ed44..7ed8dcc313 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module TypeCaster
- class Connection
+ class Connection # :nodoc:
def initialize(klass, table_name)
@klass = klass
@table_name = table_name
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
index 4b1941351c..3a367b3999 100644
--- a/activerecord/lib/active_record/type_caster/map.rb
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module TypeCaster
- class Map
+ class Map # :nodoc:
def initialize(types)
@types = types
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 32fbaf0a91..b14db85167 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,10 +2,16 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
def validate_each(record, attribute, value)
- if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
- record.errors.add(attribute, :invalid, options.merge(:value => value))
+ if Array(value).reject { |r| valid_object?(r) }.any?
+ record.errors.add(attribute, :invalid, options.merge(value: value))
end
end
+
+ private
+
+ def valid_object?(record)
+ (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
+ end
end
module ClassMethods
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 62579a4a7a..abe1ea7c90 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -23,7 +23,8 @@ module ActiveRecord
end
def test_tables
- tables = @connection.tables
+ tables = nil
+ ActiveSupport::Deprecation.silence { tables = @connection.tables }
assert tables.include?("accounts")
assert tables.include?("authors")
assert tables.include?("tasks")
@@ -31,9 +32,15 @@ module ActiveRecord
end
def test_table_exists?
- assert @connection.table_exists?("accounts")
- assert !@connection.table_exists?("nonexistingtable")
- assert !@connection.table_exists?(nil)
+ ActiveSupport::Deprecation.silence do
+ assert @connection.table_exists?("accounts")
+ assert !@connection.table_exists?("nonexistingtable")
+ assert !@connection.table_exists?(nil)
+ end
+ end
+
+ def test_table_exists_checking_both_tables_and_views_is_deprecated
+ assert_deprecated { @connection.table_exists?("accounts") }
end
def test_data_sources
@@ -151,14 +158,16 @@ module ActiveRecord
def test_uniqueness_violations_are_translated_to_specific_exception
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
- assert_raises(ActiveRecord::RecordNotUnique) do
+ error = assert_raises(ActiveRecord::RecordNotUnique) do
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
end
+
+ assert_not_nil error.cause
end
unless current_adapter?(:SQLite3Adapter)
def test_foreign_key_violations_are_translated_to_specific_exception
- assert_raises(ActiveRecord::InvalidForeignKey) do
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
@@ -167,6 +176,8 @@ module ActiveRecord
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
end
+
+ assert_not_nil error.cause
end
def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
@@ -174,11 +185,13 @@ module ActiveRecord
self.table_name = 'fk_test_has_fk'
end
- assert_raises(ActiveRecord::InvalidForeignKey) do
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
has_fk = klass_has_fk.new
has_fk.fk_id = 1231231231
has_fk.save(validate: false)
end
+
+ assert_not_nil error.cause
end
end
@@ -231,13 +244,25 @@ module ActiveRecord
unless current_adapter?(:PostgreSQLAdapter)
def test_log_invalid_encoding
- assert_raise ActiveRecord::StatementInvalid do
+ error = assert_raise ActiveRecord::StatementInvalid do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
raise 'ы'.force_encoding(Encoding::ASCII_8BIT)
end
end
+
+ assert_not_nil error.cause
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :SQLite3Adapter)
+ def test_tables_returning_both_tables_and_views_is_deprecated
+ assert_deprecated { @connection.tables }
end
end
+
+ def test_passing_arguments_to_tables_is_deprecated
+ assert_deprecated { @connection.tables(:books) }
+ end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index decac9e83b..390dd15b92 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -170,6 +170,34 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase
end
end
+ def test_get_and_release_advisory_lock
+ lock_name = "test_lock_name"
+
+ got_lock = @connection.get_advisory_lock(lock_name)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ assert_equal test_lock_free(lock_name), false,
+ "expected the test advisory lock to be held but it wasn't"
+
+ released_lock = @connection.release_advisory_lock(lock_name)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
+ end
+
+ def test_release_non_existent_advisory_lock
+ lock_name = "fake_lock_name"
+ released_non_existent_lock = @connection.release_advisory_lock(lock_name)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+
+ protected
+
+ def test_lock_free(lock_name)
+ @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == '1'
+ end
+
private
def with_example_table(&block)
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 2e18f609fd..14dbdd375b 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -14,38 +14,30 @@ module ActiveRecord
@db_name = db
@omgpost = Class.new(ActiveRecord::Base) do
+ self.inheritance_column = :disabled
self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
-
- @connection.create_table "mysql_doubles"
- end
-
- teardown do
- @connection.drop_table "mysql_doubles", if_exists: true
- end
-
- class MysqlDouble < ActiveRecord::Base
- self.table_name = "mysql_doubles"
end
def test_float_limits
- @connection.add_column :mysql_doubles, :float_no_limit, :float
- @connection.add_column :mysql_doubles, :float_short, :float, limit: 5
- @connection.add_column :mysql_doubles, :float_long, :float, limit: 53
-
- @connection.add_column :mysql_doubles, :float_23, :float, limit: 23
- @connection.add_column :mysql_doubles, :float_24, :float, limit: 24
- @connection.add_column :mysql_doubles, :float_25, :float, limit: 25
- MysqlDouble.reset_column_information
+ @connection.create_table :mysql_doubles do |t|
+ t.float :float_no_limit
+ t.float :float_short, limit: 5
+ t.float :float_long, limit: 53
+
+ t.float :float_23, limit: 23
+ t.float :float_24, limit: 24
+ t.float :float_25, limit: 25
+ end
- column_no_limit = MysqlDouble.columns.find { |c| c.name == 'float_no_limit' }
- column_short = MysqlDouble.columns.find { |c| c.name == 'float_short' }
- column_long = MysqlDouble.columns.find { |c| c.name == 'float_long' }
+ column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
+ column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
+ column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
- column_23 = MysqlDouble.columns.find { |c| c.name == 'float_23' }
- column_24 = MysqlDouble.columns.find { |c| c.name == 'float_24' }
- column_25 = MysqlDouble.columns.find { |c| c.name == 'float_25' }
+ column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
+ column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
+ column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
# Mysql floats are precision 0..24, Mysql doubles are precision 25..53
assert_equal 24, column_no_limit.limit
@@ -55,6 +47,8 @@ module ActiveRecord
assert_equal 24, column_23.limit
assert_equal 24, column_24.limit
assert_equal 53, column_25.limit
+ ensure
+ @connection.drop_table "mysql_doubles", if_exists: true
end
def test_schema
@@ -65,13 +59,13 @@ module ActiveRecord
assert_equal 'id', @omgpost.primary_key
end
- def test_table_exists?
+ def test_data_source_exists?
name = @omgpost.table_name
- assert @connection.table_exists?(name), "#{name} table should exist"
+ assert @connection.data_source_exists?(name), "#{name} data_source should exist"
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb
index 579c3273c6..7849248dcc 100644
--- a/activerecord/test/cases/adapters/mysql/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql/sp_test.rb
@@ -7,23 +7,24 @@ class MysqlStoredProcedureTest < ActiveRecord::MysqlTestCase
def setup
@connection = ActiveRecord::Base.connection
+ unless ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
+ skip("no stored procedure support")
+ end
end
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
# http://dev.mysql.com/doc/refman/5.6/en/call.html
- if ActiveRecord::Base.connection.version >= '5.6.0' || Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
- def test_multi_results
- rows = @connection.select_rows('CALL ten();')
- assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
- end
+ def test_multi_results
+ rows = @connection.select_rows('CALL ten();')
+ assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
+ assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
+ end
- def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
- assert_equal 3, topics.size
- assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"
- end
+ def test_multi_results_from_find_by_sql
+ topics = Topic.find_by_sql 'CALL topics(3);'
+ assert_equal 3, topics.size
+ assert @connection.active?, "Bad connection use by 'MysqlAdapter.select'"
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 31dc69a45b..b97eb3e228 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -17,7 +17,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def test_add_index
# add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
- def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -60,7 +60,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_index_in_create
- def (ActiveRecord::Base.connection).table_exists?(*); false; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
@@ -78,7 +78,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_index_in_bulk_change
- def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).data_source_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
@@ -152,7 +152,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false)
+ ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 000bcadebe..507d024bb6 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -131,4 +131,32 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
+
+ def test_get_and_release_advisory_lock
+ lock_name = "test_lock_name"
+
+ got_lock = @connection.get_advisory_lock(lock_name)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ assert_equal test_lock_free(lock_name), false,
+ "expected the test advisory lock to be held but it wasn't"
+
+ released_lock = @connection.release_advisory_lock(lock_name)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ assert test_lock_free(lock_name), 'expected the test lock to be available after releasing'
+ end
+
+ def test_release_non_existent_advisory_lock
+ lock_name = "fake_lock_name"
+ released_non_existent_lock = @connection.release_advisory_lock(lock_name)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+
+ protected
+
+ def test_lock_free(lock_name)
+ @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index faf2acb9cb..43957791b1 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -14,11 +14,43 @@ module ActiveRecord
@db_name = db
@omgpost = Class.new(ActiveRecord::Base) do
+ self.inheritance_column = :disabled
self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
end
+ def test_float_limits
+ @connection.create_table :mysql_doubles do |t|
+ t.float :float_no_limit
+ t.float :float_short, limit: 5
+ t.float :float_long, limit: 53
+
+ t.float :float_23, limit: 23
+ t.float :float_24, limit: 24
+ t.float :float_25, limit: 25
+ end
+
+ column_no_limit = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_no_limit' }
+ column_short = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_short' }
+ column_long = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_long' }
+
+ column_23 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_23' }
+ column_24 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_24' }
+ column_25 = @connection.columns(:mysql_doubles).find { |c| c.name == 'float_25' }
+
+ # Mysql floats are precision 0..24, Mysql doubles are precision 25..53
+ assert_equal 24, column_no_limit.limit
+ assert_equal 24, column_short.limit
+ assert_equal 53, column_long.limit
+
+ assert_equal 24, column_23.limit
+ assert_equal 24, column_24.limit
+ assert_equal 53, column_25.limit
+ ensure
+ @connection.drop_table "mysql_doubles", if_exists: true
+ end
+
def test_schema
assert @omgpost.first
end
@@ -27,13 +59,13 @@ module ActiveRecord
assert_equal 'id', @omgpost.primary_key
end
- def test_table_exists?
+ def test_data_source_exists?
name = @omgpost.table_name
- assert @connection.table_exists?(name), "#{name} table should exist"
+ assert @connection.data_source_exists?(name), "#{name} data_source should exist"
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("#{@db_name}.zomg"), "data_source should not exist")
end
def test_dump_indexes
diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb
index 8b12945f24..cdaa2cca44 100644
--- a/activerecord/test/cases/adapters/mysql2/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb
@@ -7,23 +7,24 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase
def setup
@connection = ActiveRecord::Base.connection
+ unless ActiveRecord::Base.connection.version >= '5.6.0'
+ skip("no stored procedure support")
+ end
end
# Test that MySQL allows multiple results for stored procedures
#
# In MySQL 5.6, CLIENT_MULTI_RESULTS is enabled by default.
# http://dev.mysql.com/doc/refman/5.6/en/call.html
- if ActiveRecord::Base.connection.version >= '5.6.0'
- def test_multi_results
- rows = @connection.select_rows('CALL ten();')
- assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
- assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
- end
+ def test_multi_results
+ rows = @connection.select_rows('CALL ten();')
+ assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'"
+ end
- def test_multi_results_from_find_by_sql
- topics = Topic.find_by_sql 'CALL topics(3);'
- assert_equal 3, topics.size
- assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'"
- end
+ def test_multi_results_from_find_by_sql
+ topics = Topic.find_by_sql 'CALL topics(3);'
+ assert_equal 3, topics.size
+ assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select'"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 722e2377c1..d559de3e28 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -90,7 +90,7 @@ module ActiveRecord
end
def test_tables_logs_name
- @connection.tables('hello')
+ ActiveSupport::Deprecation.silence { @connection.tables('hello') }
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
@@ -100,7 +100,7 @@ module ActiveRecord
end
def test_table_exists_logs_name
- @connection.table_exists?('items')
+ ActiveSupport::Deprecation.silence { @connection.table_exists?('items') }
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
@@ -209,5 +209,47 @@ module ActiveRecord
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
end
end
+
+ def test_get_and_release_advisory_lock
+ lock_id = 5295901941911233559
+ list_advisory_locks = <<-SQL
+ SELECT locktype,
+ (classid::bigint << 32) | objid::bigint AS lock_id
+ FROM pg_locks
+ WHERE locktype = 'advisory'
+ SQL
+
+ got_lock = @connection.get_advisory_lock(lock_id)
+ assert got_lock, "get_advisory_lock should have returned true but it didn't"
+
+ advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == lock_id}
+ assert advisory_lock,
+ "expected to find an advisory lock with lock_id #{lock_id} but there wasn't one"
+
+ released_lock = @connection.release_advisory_lock(lock_id)
+ assert released_lock, "expected release_advisory_lock to return true but it didn't"
+
+ advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == lock_id}
+ assert_empty advisory_locks,
+ "expected to have released advisory lock with lock_id #{lock_id} but it was still held"
+ end
+
+ def test_release_non_existent_advisory_lock
+ fake_lock_id = 2940075057017742022
+ with_warning_suppression do
+ released_non_existent_lock = @connection.release_advisory_lock(fake_lock_id)
+ assert_equal released_non_existent_lock, false,
+ 'expected release_advisory_lock to return false when there was no lock to release'
+ end
+ end
+
+ protected
+
+ def with_warning_suppression
+ log_level = @connection.client_min_messages
+ @connection.client_min_messages = 'error'
+ yield
+ @connection.client_min_messages = log_level
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 93e98ec872..7c9169f6e2 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -184,42 +184,42 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
end
- def test_table_exists?
+ def test_data_source_exists?
[Thing1, Thing2, Thing3, Thing4].each do |klass|
name = klass.table_name
- assert @connection.table_exists?(name), "'#{name}' table should exist"
+ assert @connection.data_source_exists?(name), "'#{name}' data_source should exist"
end
end
- def test_table_exists_when_on_schema_search_path
+ def test_data_source_exists_when_on_schema_search_path
with_schema_search_path(SCHEMA_NAME) do
- assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found")
+ assert(@connection.data_source_exists?(TABLE_NAME), "data_source should exist and be found")
end
end
- def test_table_exists_when_not_on_schema_search_path
+ def test_data_source_exists_when_not_on_schema_search_path
with_schema_search_path('PUBLIC') do
- assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found")
+ assert(!@connection.data_source_exists?(TABLE_NAME), "data_source exists but should not be found")
end
end
- def test_table_exists_wrong_schema
- assert(!@connection.table_exists?("foo.things"), "table should not exist")
+ def test_data_source_exists_wrong_schema
+ assert(!@connection.data_source_exists?("foo.things"), "data_source should not exist")
end
- def test_table_exists_quoted_names
+ def test_data_source_exists_quoted_names
[ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given|
- assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
end
with_schema_search_path(SCHEMA_NAME) do
given = %("#{TABLE_NAME}")
- assert(@connection.table_exists?(given), "table should exist when specified as #{given}")
+ assert(@connection.data_source_exists?(given), "data_source should exist when specified as #{given}")
end
end
- def test_table_exists_quoted_table
+ def test_data_source_exists_quoted_table
with_schema_search_path(SCHEMA_NAME) do
- assert(@connection.table_exists?('"things.table"'), "table should exist")
+ assert(@connection.data_source_exists?('"things.table"'), "data_source should exist")
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 640df31e2e..a2fd1177a6 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -284,9 +284,9 @@ module ActiveRecord
def test_tables
with_example_table do
- assert_equal %w{ ex }, @conn.tables
+ ActiveSupport::Deprecation.silence { assert_equal %w{ ex }, @conn.tables }
with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do
- assert_equal %w{ ex people }.sort, @conn.tables.sort
+ ActiveSupport::Deprecation.silence { assert_equal %w{ ex people }.sort, @conn.tables.sort }
end
end
end
@@ -297,7 +297,9 @@ module ActiveRecord
WHERE type IN ('table','view') AND name <> 'sqlite_sequence'
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
- @conn.tables('hello')
+ ActiveSupport::Deprecation.silence do
+ @conn.tables('hello')
+ end
end
end
@@ -316,7 +318,9 @@ module ActiveRecord
WHERE type IN ('table','view') AND name <> 'sqlite_sequence' AND name = 'ex'
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
- assert @conn.table_exists?('ex')
+ ActiveSupport::Deprecation.silence do
+ assert @conn.table_exists?('ex')
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
new file mode 100644
index 0000000000..2b867965ba
--- /dev/null
+++ b/activerecord/test/cases/associations/bidirectional_destroy_dependencies_test.rb
@@ -0,0 +1,41 @@
+require 'cases/helper'
+require 'models/content'
+
+class BidirectionalDestroyDependenciesTest < ActiveRecord::TestCase
+ fixtures :content, :content_positions
+
+ def setup
+ Content.destroyed_ids.clear
+ ContentPosition.destroyed_ids.clear
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_belongs_to_association
+ content_position = ContentPosition.find(1)
+ content = content_position.content
+ assert_not_nil content
+
+ content_position.destroy
+
+ assert_equal [content_position.id], ContentPosition.destroyed_ids
+ assert_equal [content.id], Content.destroyed_ids
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_has_one_association
+ content = Content.find(1)
+ content_position = content.content_position
+ assert_not_nil content_position
+
+ content.destroy
+
+ assert_equal [content.id], Content.destroyed_ids
+ assert_equal [content_position.id], ContentPosition.destroyed_ids
+ end
+
+ def test_bidirectional_dependence_when_destroying_item_with_has_one_association_fails_first_time
+ content = ContentWhichRequiresTwoDestroyCalls.find(1)
+
+ 2.times { content.destroy }
+
+ assert_equal content.destroyed?, true
+ end
+end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index bc1bff79d3..0c09713971 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1179,6 +1179,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 1, mary.unique_categorized_post_ids.length
end
+ def test_preloading_has_one_using_reorder
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; "TempAuthor"; end
+ self.table_name = "authors"
+ has_one :post, class_name: "PostWithDefaultScope", foreign_key: :author_id
+ has_one :reorderd_post, -> { reorder(title: :desc) }, class_name: "PostWithDefaultScope", foreign_key: :author_id
+ end
+
+ author = klass.first
+ # PRECONDITION: make sure ordering results in different results
+ assert_not_equal author.post, author.reorderd_post
+
+ preloaded_reorderd_post = klass.preload(:reorderd_post).first.reorderd_post
+
+ assert_equal author.reorderd_post, preloaded_reorderd_post
+ assert_equal Post.order(title: :desc).first.title, preloaded_reorderd_post.title
+ end
+
def test_preloading_polymorphic_with_custom_foreign_type
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
groucho = members(:groucho)
@@ -1203,12 +1221,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def test_join_eager_with_nil_order_should_generate_valid_sql
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
- Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
- end
- end
-
def test_deep_including_through_habtm
# warm up habtm cache
posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 20af436e02..ccb062f991 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -957,4 +957,29 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
projects = ProjectUnscopingDavidDefaultScope.includes(:developers).where(id: project.id)
assert_equal 1, projects.first.developers.size
end
+
+ def test_preloaded_associations_size
+ assert_equal Project.first.salaried_developers.size,
+ Project.preload(:salaried_developers).first.salaried_developers.size
+
+ assert_equal Project.includes(:salaried_developers).references(:salaried_developers).first.salaried_developers.size,
+ Project.preload(:salaried_developers).first.salaried_developers.size
+
+ # Nested HATBM
+ first_project = Developer.first.projects.first
+ preloaded_first_project =
+ Developer.preload(projects: :salaried_developers).
+ first.
+ projects.
+ detect { |p| p.id == first_project.id }
+
+ assert preloaded_first_project.salaried_developers.loaded?, true
+ assert_equal first_project.salaried_developers.size, preloaded_first_project.salaried_developers.size
+ end
+
+ def test_has_and_belongs_to_many_is_useable_with_belongs_to_required_by_default
+ assert_difference "Project.first.developers_required_by_default.size", 1 do
+ Project.first.developers_required_by_default.create!(name: "Sean", salary: 50000)
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index eb94870a35..50ca6537cc 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -203,9 +203,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
bulb = car.bulbs.create
assert_equal 'defaulty', bulb.name
+ end
+
+ def test_build_and_create_from_association_should_respect_passed_attributes_over_default_scope
+ car = Car.create(name: 'honda')
+
+ bulb = car.bulbs.build(name: 'exotic')
+ assert_equal 'exotic', bulb.name
- bulb = car.bulbs.create(:name => 'exotic')
+ bulb = car.bulbs.create(name: 'exotic')
assert_equal 'exotic', bulb.name
+
+ bulb = car.awesome_bulbs.build(frickinawesome: false)
+ assert_equal false, bulb.frickinawesome
+
+ bulb = car.awesome_bulbs.create(frickinawesome: false)
+ assert_equal false, bulb.frickinawesome
end
def test_build_from_association_should_respect_scope
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index cf730e4fe7..226ecf5447 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1111,10 +1111,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_with_default_scope_on_the_target
person = people(:michael)
- assert_equal [posts(:thinking)], person.first_posts
+ assert_equal [posts(:thinking).id], person.first_posts.map(&:id)
readers(:michael_authorless).update(first_post_id: 1)
- assert_equal [posts(:thinking)], person.reload.first_posts
+ assert_equal [posts(:thinking).id], person.reload.first_posts.map(&:id)
end
def test_has_many_through_with_includes_in_through_association_scope
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
new file mode 100644
index 0000000000..4af791b758
--- /dev/null
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -0,0 +1,79 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/author'
+require 'models/essay'
+require 'models/categorization'
+require 'models/person'
+
+class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
+ fixtures :authors, :essays, :posts, :comments, :categorizations, :people
+
+ def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
+ result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a
+ assert_equal authors(:david), result.first
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations
+ assert_nothing_raised do
+ queries = capture_sql do
+ Person.left_outer_joins(:agents => {:agents => :agents})
+ .left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a
+ end
+ assert queries.any? { |sql| /agents_people_4/i =~ sql }
+ end
+ end
+
+ def test_construct_finder_sql_executes_a_left_outer_join
+ assert_not_equal Author.count, Author.joins(:posts).count
+ assert_equal Author.count, Author.left_outer_joins(:posts).count
+ end
+
+ def test_left_outer_join_by_left_joins
+ assert_not_equal Author.count, Author.joins(:posts).count
+ assert_equal Author.count, Author.left_joins(:posts).count
+ end
+
+ def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
+ queries = capture_sql { Author.left_outer_joins({}) }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ end
+
+ def test_construct_finder_sql_ignores_empty_left_outer_joins_array
+ queries = capture_sql { Author.left_outer_joins([]) }
+ assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql }
+ end
+
+ def test_left_outer_joins_forbids_to_use_string_as_argument
+ assert_raise(ArgumentError){ Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a }
+ end
+
+ def test_join_conditions_added_to_join_clause
+ queries = capture_sql { Author.left_outer_joins(:essays).to_a }
+ assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql }
+ assert queries.none? { |sql| /WHERE/i =~ sql }
+ end
+
+ def test_find_with_sti_join
+ scope = Post.left_outer_joins(:special_comments).where(:id => posts(:sti_comments).id)
+
+ # The join should match SpecialComment and its subclasses only
+ assert scope.where("comments.type" => "Comment").empty?
+ assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ end
+
+ def test_does_not_override_select
+ authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts)
+ assert authors.any?
+ assert authors.first.respond_to?(:addr_id)
+ end
+
+ test "the default scope of the target is applied when joining associations" do
+ author = Author.create! name: "Jon"
+ author.categorizations.create!
+ author.categorizations.create! special: true
+
+ assert_equal [author], Author.where(id: author).left_outer_joins(:special_categorizations)
+ end
+end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 264b275181..2991ca8b76 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -172,5 +172,18 @@ module ActiveRecord
assert_equal int_range, klass.type_for_attribute("my_int_range")
end
end
+
+ test "attributes added after subclasses load are inherited" do
+ parent = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ end
+
+ child = Class.new(parent)
+ child.new # => force a schema load
+
+ parent.attribute(:foo, Type::Value.new)
+
+ assert_equal(:bar, child.new(foo: :bar).foo)
+ end
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 37ec3f1106..0df8f1f798 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -24,6 +24,8 @@ require 'models/molecule'
require 'models/member'
require 'models/member_detail'
require 'models/organization'
+require 'models/guitar'
+require 'models/tuning_peg'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_validation
@@ -397,6 +399,40 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
end
+ def test_errors_should_be_indexed_when_passed_as_array
+ guitar = Guitar.new
+ tuning_peg_valid = TuningPeg.new
+ tuning_peg_valid.pitch = 440.0
+ tuning_peg_invalid = TuningPeg.new
+
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
+
+ assert_not tuning_peg_invalid.valid?
+ assert tuning_peg_valid.valid?
+ assert_not guitar.valid?
+ assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"]
+ assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"]
+ end
+
+ def test_errors_should_be_indexed_when_global_flag_is_set
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
+ ActiveRecord::Base.index_nested_attribute_errors = true
+
+ molecule = Molecule.new
+ valid_electron = Electron.new(name: 'electron')
+ invalid_electron = Electron.new
+
+ molecule.electrons = [valid_electron, invalid_electron]
+
+ assert_not invalid_electron.valid?
+ assert valid_electron.valid?
+ assert_not molecule.valid?
+ assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
+ assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
+ ensure
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
+ end
+
def test_valid_adding_with_nested_attributes
molecule = Molecule.new
valid_electron = Electron.new(name: 'electron')
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index dbbcaa075d..d961f4710e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -26,7 +26,7 @@ require 'models/bird'
require 'models/car'
require 'models/bulb'
require 'rexml/document'
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
class FirstAbstractClass < ActiveRecord::Base
self.abstract_class = true
@@ -1204,42 +1204,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal last, Developer.all.merge!(:order => :salary).to_a.last
end
- def test_abstract_class
- assert !ActiveRecord::Base.abstract_class?
- assert LoosePerson.abstract_class?
- assert !LooseDescendant.abstract_class?
- end
-
def test_abstract_class_table_name
assert_nil AbstractCompany.table_name
end
- def test_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
-
- # Abstract subclass of AR::Base.
- assert LoosePerson.descends_from_active_record?
-
- # Concrete subclass of an abstract class.
- assert LooseDescendant.descends_from_active_record?
-
- # Concrete subclass of AR::Base.
- assert TightPerson.descends_from_active_record?
-
- # Concrete subclass of a concrete class but has no type column.
- assert TightDescendant.descends_from_active_record?
-
- # Concrete subclass of AR::Base.
- assert Post.descends_from_active_record?
-
- # Abstract subclass of a concrete class which has a type column.
- # This is pathological, as you'll never have Sub < Abstract < Concrete.
- assert !StiPost.descends_from_active_record?
-
- # Concrete subclasses an abstract class which has a type column.
- assert !SubStiPost.descends_from_active_record?
- end
-
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
@@ -1284,53 +1252,6 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
- def test_compute_type_success
- assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
- end
-
- def test_compute_type_nonexistent_constant
- e = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, 'NonexistentModel'
- end
- assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
- assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
- end
-
- def test_compute_type_no_method_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
- assert_raises NoMethodError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- end
- end
-
- def test_compute_type_on_undefined_method
- error = nil
- begin
- Class.new(Author) do
- alias_method :foo, :bar
- end
- rescue => e
- error = e
- end
-
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do
-
- exception = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- assert_equal error.message, exception.message
- end
- end
-
- def test_compute_type_argument_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
- assert_raises ArgumentError do
- ActiveRecord::Base.send :compute_type, 'InvalidModel'
- end
- end
- end
-
def test_clear_cache!
# preheat cache
c1 = Post.connection.schema_cache.columns('posts')
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 9cb70ee239..da65336305 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -161,7 +161,7 @@ class EachTest < ActiveRecord::TestCase
end
# posts.first will be ordered using id only. Title order scope should not apply here
assert_not_equal first_post, posts.first
- assert_equal posts(:welcome), posts.first
+ assert_equal posts(:welcome).id, posts.first.id
end
def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index dff6ea0fb0..d43668e57c 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -92,7 +92,14 @@ module ActiveRecord
app = lambda { |_| [200, {}, body] }
response_body = ConnectionManagement.new(app).call(@env)[2]
assert response_body.respond_to?(:to_path)
- assert_equal response_body.to_path, "/path"
+ assert_equal "/path", response_body.to_path
+ end
+
+ test "doesn't mutate the original response" do
+ original_response = [200, {}, 'hi']
+ app = lambda { |_| original_response }
+ ConnectionManagement.new(app).call(@env)[2]
+ assert_equal 'hi', original_response.last
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7ef5c93a48..efa3e0455e 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index a0eaa66e94..f30ed4fcc8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -955,3 +955,17 @@ class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase
assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate
end
end
+
+class FixtureClassNamesTest < ActiveRecord::TestCase
+ def setup
+ @saved_cache = self.fixture_class_names.dup
+ end
+
+ def teardown
+ self.fixture_class_names.replace(@saved_cache)
+ end
+
+ test "fixture_class_names returns nil for unregistered identifier" do
+ assert_nil self.fixture_class_names['unregistered_identifier']
+ end
+end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index f67d85603a..52e3734dd0 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'models/author'
require 'models/company'
require 'models/person'
require 'models/post'
@@ -55,6 +56,53 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
+ def test_compute_type_success
+ assert_equal Author, ActiveRecord::Base.send(:compute_type, 'Author')
+ end
+
+ def test_compute_type_nonexistent_constant
+ e = assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'NonexistentModel'
+ end
+ assert_equal 'uninitialized constant ActiveRecord::Base::NonexistentModel', e.message
+ assert_equal 'ActiveRecord::Base::NonexistentModel', e.name
+ end
+
+ def test_compute_type_no_method_error
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
+ assert_raises NoMethodError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ end
+ end
+
+ def test_compute_type_on_undefined_method
+ error = nil
+ begin
+ Class.new(Author) do
+ alias_method :foo, :bar
+ end
+ rescue => e
+ error = e
+ end
+
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do
+
+ exception = assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ assert_equal error.message, exception.message
+ end
+ end
+
+ def test_compute_type_argument_error
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
+ assert_raises ArgumentError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ end
+ end
+
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
without_store_full_sti_class do
item = Namespaced::Company.new
@@ -77,6 +125,32 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
+ def test_descends_from_active_record
+ assert !ActiveRecord::Base.descends_from_active_record?
+
+ # Abstract subclass of AR::Base.
+ assert LoosePerson.descends_from_active_record?
+
+ # Concrete subclass of an abstract class.
+ assert LooseDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert TightPerson.descends_from_active_record?
+
+ # Concrete subclass of a concrete class but has no type column.
+ assert TightDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert Post.descends_from_active_record?
+
+ # Abstract subclass of a concrete class which has a type column.
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
+ assert !StiPost.descends_from_active_record?
+
+ # Concrete subclasses an abstract class which has a type column.
+ assert !SubStiPost.descends_from_active_record?
+ end
+
def test_company_descends_from_active_record
assert !ActiveRecord::Base.descends_from_active_record?
assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
@@ -84,6 +158,12 @@ class InheritanceTest < ActiveRecord::TestCase
assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
end
+ def test_abstract_class
+ assert !ActiveRecord::Base.abstract_class?
+ assert LoosePerson.abstract_class?
+ assert !LooseDescendant.abstract_class?
+ end
+
def test_inheritance_base_class
assert_equal Post, Post.base_class
assert_equal Post, SpecialPost.base_class
@@ -223,7 +303,6 @@ class InheritanceTest < ActiveRecord::TestCase
end
end
-
def test_new_with_complex_inheritance
assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 84b0ff8fcb..0da58040c8 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -157,8 +157,10 @@ module ActiveRecord
teardown do
%w[horses new_horses].each do |table|
- if ActiveRecord::Base.connection.table_exists?(table)
- ActiveRecord::Base.connection.drop_table(table)
+ ActiveSupport::Deprecation.silence do
+ if ActiveRecord::Base.connection.table_exists?(table)
+ ActiveRecord::Base.connection.drop_table(table)
+ end
end
end
ActiveRecord::Migration.verbose = @verbose_was
@@ -189,14 +191,14 @@ module ActiveRecord
def test_migrate_up
migration = InvertibleMigration.new
migration.migrate(:up)
- assert migration.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses"), "horses should exist" }
end
def test_migrate_down
migration = InvertibleMigration.new
migration.migrate :up
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
def test_migrate_revert
@@ -204,11 +206,11 @@ module ActiveRecord
revert = InvertibleRevertMigration.new
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
revert.migrate :down
- assert migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
def test_migrate_revert_by_part
@@ -216,18 +218,24 @@ module ActiveRecord
received = []
migration = InvertibleByPartsMigration.new
migration.test = ->(dir){
- assert migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ end
received << dir
}
migration.migrate :up
assert_equal [:both, :up], received
- assert !migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert !migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ end
migration.migrate :down
assert_equal [:both, :up, :both, :down], received
- assert migration.connection.table_exists?("horses")
- assert !migration.connection.table_exists?("new_horses")
+ ActiveSupport::Deprecation.silence do
+ assert migration.connection.table_exists?("horses")
+ assert !migration.connection.table_exists?("new_horses")
+ end
end
def test_migrate_revert_whole_migration
@@ -236,20 +244,20 @@ module ActiveRecord
revert = RevertWholeMigration.new(klass)
migration.migrate :up
revert.migrate :up
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
revert.migrate :down
- assert migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
migration.migrate :down
- assert !migration.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
end
end
def test_migrate_nested_revert_whole_migration
revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)
revert.migrate :down
- assert revert.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert revert.connection.table_exists?("horses") }
revert.migrate :up
- assert !revert.connection.table_exists?("horses")
+ ActiveSupport::Deprecation.silence { assert !revert.connection.table_exists?("horses") }
end
def test_migrate_revert_change_column_default
@@ -314,24 +322,24 @@ module ActiveRecord
def test_legacy_up
LegacyMigration.migrate :up
- assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
end
def test_legacy_down
LegacyMigration.migrate :up
LegacyMigration.migrate :down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
end
def test_up
LegacyMigration.up
- assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
end
def test_down
LegacyMigration.up
LegacyMigration.down
- assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
end
def test_migrate_down_with_table_name_prefix
@@ -340,7 +348,7 @@ module ActiveRecord
migration = InvertibleMigration.new
migration.migrate(:up)
assert_nothing_raised { migration.migrate(:down) }
- assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
+ ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" }
ensure
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 3846ba8e7f..707a2d1da1 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -209,7 +209,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
- unless current_adapter?(:Mysql2Adapter)
+ if ActiveRecord::Base.connection.prepared_statements
def test_binary_data_is_not_logged
Binary.create(data: 'some binary data')
wait
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 83e50048ec..2ff9cf8cf5 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -405,9 +405,9 @@ module ActiveRecord
def test_drop_table_if_exists
connection.create_table(:testings)
- assert connection.table_exists?(:testings)
+ ActiveSupport::Deprecation.silence { assert connection.table_exists?(:testings) }
connection.drop_table(:testings, if_exists: true)
- assert_not connection.table_exists?(:testings)
+ ActiveSupport::Deprecation.silence { assert_not connection.table_exists?(:testings) }
end
def test_drop_table_if_exists_nothing_raised
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 8fd08fe4ce..0a7b57455c 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -12,7 +12,9 @@ module ActiveRecord
teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
- connection.drop_table table_name if connection.tables.include?(table_name)
+ ActiveSupport::Deprecation.silence do
+ connection.drop_table table_name if connection.table_exists?(table_name)
+ end
end
end
@@ -82,62 +84,62 @@ module ActiveRecord
connection.create_join_table :artists, :musics
connection.drop_join_table :artists, :musics
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_drop_join_table_with_strings
connection.create_join_table :artists, :musics
connection.drop_join_table 'artists', 'musics'
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_drop_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
connection.drop_join_table :videos, :musics
- assert !connection.tables.include?('musics_videos')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('musics_videos') }
end
def test_drop_join_table_with_the_table_name
connection.create_join_table :artists, :musics, table_name: :catalog
connection.drop_join_table :artists, :musics, table_name: :catalog
- assert !connection.tables.include?('catalog')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('catalog') }
end
def test_drop_join_table_with_the_table_name_as_string
connection.create_join_table :artists, :musics, table_name: 'catalog'
connection.drop_join_table :artists, :musics, table_name: 'catalog'
- assert !connection.tables.include?('catalog')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('catalog') }
end
def test_drop_join_table_with_column_options
connection.create_join_table :artists, :musics, column_options: {null: true}
connection.drop_join_table :artists, :musics, column_options: {null: true}
- assert !connection.tables.include?('artists_musics')
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('artists_musics') }
end
def test_create_and_drop_join_table_with_common_prefix
with_table_cleanup do
connection.create_join_table 'audio_artists', 'audio_musics'
- assert_includes connection.tables, 'audio_artists_musics'
+ ActiveSupport::Deprecation.silence { assert connection.table_exists?('audio_artists_musics') }
connection.drop_join_table 'audio_artists', 'audio_musics'
- assert !connection.tables.include?('audio_artists_musics'), "Should have dropped join table, but didn't"
+ ActiveSupport::Deprecation.silence { assert !connection.table_exists?('audio_artists_musics'), "Should have dropped join table, but didn't" }
end
end
private
def with_table_cleanup
- tables_before = connection.tables
+ tables_before = connection.data_sources
yield
ensure
- tables_after = connection.tables - tables_before
+ tables_after = connection.data_sources - tables_before
tables_after.each do |table|
connection.drop_table table
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 84ec657398..edbc8abe4d 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -164,7 +164,7 @@ class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
t.references :testing_parent, foreign_key: true
end
- assert_includes @connection.tables, "testings"
+ assert_includes @connection.data_sources, "testings"
end
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 6d742d3f2f..8eb027d53f 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def teardown
- rename_table :octopi, :test_models if connection.table_exists? :octopi
+ ActiveSupport::Deprecation.silence { rename_table :octopi, :test_models if connection.table_exists? :octopi }
super
end
@@ -83,7 +83,7 @@ module ActiveRecord
enable_extension!('uuid-ossp', connection)
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
- assert connection.table_exists? :felines
+ ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines }
ensure
disable_extension!('uuid-ossp', connection)
connection.drop_table :cats, if_exists: true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 10f1c7216f..c3c204cf9f 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1,6 +1,7 @@
-require "cases/helper"
-require "cases/migration/helper"
+require 'cases/helper'
+require 'cases/migration/helper'
require 'bigdecimal/util'
+require 'concurrent/atomic/count_down_latch'
require 'models/person'
require 'models/topic'
@@ -522,6 +523,79 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ if ActiveRecord::Base.connection.supports_advisory_locks?
+ def test_migrator_generates_valid_lock_id
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ assert ActiveRecord::Base.connection.get_advisory_lock(lock_id),
+ "the Migrator should have generated a valid lock id, but it didn't"
+ assert ActiveRecord::Base.connection.release_advisory_lock(lock_id),
+ "the Migrator should have generated a valid lock id, but it didn't"
+ end
+
+ def test_generate_migrator_advisory_lock_id
+ # It is important we are consistent with how we generate this so that
+ # exclusive locking works across migrator versions
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ current_database = ActiveRecord::Base.connection.current_database
+ salt = ActiveRecord::Migrator::MIGRATOR_SALT
+ expected_id = Zlib.crc32(current_database) * salt
+
+ assert lock_id == expected_id, "expected lock id generated by the migrator to be #{expected_id}, but it was #{lock_id} instead"
+ assert lock_id.is_a?(Fixnum), "expected lock id to be a Fixnum, but it wasn't"
+ assert lock_id.bit_length <= 63, "lock id must be a signed integer of max 63 bits magnitude"
+ end
+
+ def test_migrator_one_up_with_unavailable_lock
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ with_another_process_holding_lock(lock_id) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.migrate }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+
+ def test_migrator_one_up_with_unavailable_lock_using_run
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_id = migrator.send(:generate_migrator_advisory_lock_id)
+
+ with_another_process_holding_lock(lock_id) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.run }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+ end
+
protected
# This is needed to isolate class_attribute assignments like `table_name_prefix`
# for each test case.
@@ -531,6 +605,30 @@ class MigrationTest < ActiveRecord::TestCase
def self.base_class; self; end
}
end
+
+ def with_another_process_holding_lock(lock_id)
+ thread_lock = Concurrent::CountDownLatch.new
+ test_terminated = Concurrent::CountDownLatch.new
+
+ other_process = Thread.new do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ conn.get_advisory_lock(lock_id)
+ thread_lock.count_down
+ test_terminated.wait # hold the lock open until we tested everything
+ ensure
+ conn.release_advisory_lock(lock_id)
+ ActiveRecord::Base.connection_pool.checkin(conn)
+ end
+ end
+
+ thread_lock.wait # wait until the 'other process' has the lock
+
+ yield
+
+ test_terminated.count_down
+ other_process.join
+ end
end
class ReservedWordsMigrationTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 2ff6938e7b..dbf088f455 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -313,9 +313,9 @@ class MigratorTest < ActiveRecord::TestCase
_, migrator = migrator_class(3)
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveSupport::Deprecation.silence { assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations') }
migrator.migrate("valid", 1)
- assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?('schema_migrations') }
end
def test_migrator_forward
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 31686bde3f..b2e59e3970 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -744,9 +744,10 @@ class PersistenceTest < ActiveRecord::TestCase
assert !topic.approved?
assert_equal "The First Topic", topic.title
- assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
+ error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
topic.update_attributes(id: 3, title: "Hm is it possible?")
end
+ assert_not_nil error.cause
assert_not_equal "Hm is it possible?", Topic.find(3).title
topic.update_attributes(id: 1234)
@@ -965,4 +966,17 @@ class PersistenceTest < ActiveRecord::TestCase
widget.reset_column_information
end
end
+
+ def test_reset_column_information_resets_children
+ child = Class.new(Topic)
+ child.new # force schema to load
+
+ ActiveRecord::Base.connection.add_column(:topics, :foo, :string)
+ Topic.reset_column_information
+
+ assert_equal "bar", child.new(foo: :bar).foo
+ ensure
+ ActiveRecord::Base.connection.remove_column(:topics, :foo)
+ Topic.reset_column_information
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index daa3271777..bca50dd008 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -44,7 +44,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
conn = ActiveRecord::Base.connection_pool.checkout
ActiveRecord::Base.connection_pool.checkin conn
@connection_count += 1
- ActiveRecord::Base.connection.tables
+ ActiveRecord::Base.connection.data_sources
rescue ActiveRecord::ConnectionTimeoutError
@timed_out += 1
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 5e4ba47988..d883784553 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -224,7 +224,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes
+ @connection.drop_table(:barcodes, if_exists: true)
end
def test_any_type_primary_key
@@ -280,6 +280,32 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
con.reconnect!
end
end
+
+ class PrimaryKeyBigintNilDefaultTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:bigint_defaults, id: :bigint, default: nil, force: true)
+ end
+
+ def teardown
+ @connection.drop_table :bigint_defaults, if_exists: true
+ end
+
+ test "primary key with bigint allows default override via nil" do
+ column = @connection.columns(:bigint_defaults).find { |c| c.name == 'id' }
+ assert column.bigint?
+ assert_not column.auto_increment?
+ end
+
+ test "schema dump primary key with bigint default nil" do
+ schema = dump_table_schema "bigint_defaults"
+ assert_match %r{create_table "bigint_defaults", id: :bigint, default: nil}, schema
+ end
+ end
end
if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 88d2dd55ab..d0f60a84b5 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -22,13 +22,17 @@ module ActiveRecord
def sanitize_sql(sql)
sql
end
+
+ def sanitize_sql_for_order(sql)
+ sql
+ end
end
def relation
@relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 675149556f..f46d414b95 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -20,6 +20,10 @@ module ActiveRecord
def self.table_name
'fake_table'
end
+
+ def self.sanitize_sql_for_order(sql)
+ sql
+ end
end
def test_construction
@@ -231,6 +235,13 @@ module ActiveRecord
assert_equal 3, relation.where(id: post.id).pluck(:id).size
end
+ def test_merge_raises_with_invalid_argument
+ assert_raises ArgumentError do
+ relation = Relation.new(FakeKlass, :b, nil)
+ relation.merge(true)
+ end
+ end
+
def test_respond_to_for_non_selected_element
post = Post.select(:title).first
assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7521f0573a..cd23c1b3e1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -297,6 +297,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 3, tags.length
end
+ def test_finding_with_sanitized_order
+ query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql
+ assert_match(/field\(id, 1,3,2\)/, query)
+ end
+
def test_finding_with_order_limit_and_offset
entrants = Entrant.order("id ASC").limit(2).offset(1)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 2a2c2bc8d0..43f133b12d 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -353,6 +353,38 @@ class SchemaDumperTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ''
$stdout = original
end
+
+ def test_schema_dump_with_table_name_prefix_and_ignoring_tables
+ original, $stdout = $stdout, StringIO.new
+
+ create_cat_migration = Class.new(ActiveRecord::Migration) do
+ def change
+ create_table("cats") do |t|
+ end
+ create_table("omg_cats") do |t|
+ end
+ end
+ end
+
+ original_table_name_prefix = ActiveRecord::Base.table_name_prefix
+ original_schema_dumper_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ ActiveRecord::Base.table_name_prefix = 'omg_'
+ ActiveRecord::SchemaDumper.ignore_tables = ["cats"]
+ migration = create_cat_migration.new
+ migration.migrate(:up)
+
+ stream = StringIO.new
+ output = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream).string
+
+ assert_match %r{create_table "omg_cats"}, output
+ refute_match %r{create_table "cats"}, output
+ ensure
+ migration.migrate(:down)
+ ActiveRecord::Base.table_name_prefix = original_table_name_prefix
+ ActiveRecord::SchemaDumper.ignore_tables = original_schema_dumper_ignore_tables
+
+ $stdout = original
+ end
end
class SchemaDumperDefaultsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index a93fa57257..723a7618ba 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -21,28 +21,21 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_creates_database_with_default_encoding_and_collation
+ def test_creates_database_with_no_default_options
@connection.expects(:create_database).
- with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci')
+ with('my-app-db', {})
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_creates_database_with_given_encoding_and_default_collation
- @connection.expects(:create_database).
- with('my-app-db', charset: 'utf8', collation: 'utf8_unicode_ci')
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'utf8')
- end
-
- def test_creates_database_with_given_encoding_and_no_collation
+ def test_creates_database_with_given_encoding
@connection.expects(:create_database).
with('my-app-db', charset: 'latin1')
ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge('encoding' => 'latin1')
end
- def test_creates_database_with_given_collation_and_no_encoding
+ def test_creates_database_with_given_collation
@connection.expects(:create_database).
with('my-app-db', collation: 'latin1_swedish_ci')
@@ -111,7 +104,7 @@ module ActiveRecord
def test_database_created_by_root
assert_permissions_granted_for "pat"
@connection.expects(:create_database).
- with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
+ with('my-app-db', {})
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
@@ -203,9 +196,9 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.purge @configuration
end
- def test_recreates_database_with_the_default_options
+ def test_recreates_database_with_no_default_options
@connection.expects(:recreate_database).
- with('test-db', charset: 'utf8', collation: 'utf8_unicode_ci')
+ with('test-db', {})
ActiveRecord::Tasks::DatabaseTasks.purge @configuration
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index c3fd0b2383..c31f94b2f2 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -60,7 +60,7 @@ module ActiveRecord
$stderr.expects(:puts).
with("Couldn't create database for #{@configuration.inspect}")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
end
def test_create_when_database_exists_outputs_info_to_stderr
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 750d5e42dc..0aea0c3b38 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -53,7 +53,7 @@ module ActiveRecord
$stderr.expects(:puts).
with("Couldn't create database for #{@configuration.inspect}")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' }
end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index bff5ffa65e..584a3dc0d8 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -45,6 +45,18 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert t.valid?
end
+ def test_validates_associated_without_marked_for_destruction
+ reply = Class.new do
+ def valid?
+ true
+ end
+ end
+ Topic.validates_associated(:replies)
+ t = Topic.new
+ t.define_singleton_method(:replies) { [reply.new] }
+ assert t.valid?
+ end
+
def test_validates_associated_with_custom_message_using_quotes
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
Topic.validates_presence_of :content
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index e80d8bd584..d50ae74e35 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -45,7 +45,7 @@ module ViewBehavior
def test_table_exists
view_name = Ebook.table_name
# TODO: switch this assertion around once we changed #tables to not return views.
- assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
end
def test_views_ara_valid_data_sources
@@ -87,7 +87,7 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
end
def drop_view(name)
- @connection.execute "DROP VIEW #{name}" if @connection.table_exists? name
+ @connection.execute "DROP VIEW #{name}" if @connection.view_exists? name
end
end
@@ -106,7 +106,7 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute "DROP VIEW paperbacks" if @connection.table_exists? "paperbacks"
+ @connection.execute "DROP VIEW paperbacks" if @connection.view_exists? "paperbacks"
end
def test_reading
@@ -125,7 +125,8 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
def test_table_exists
view_name = Paperback.table_name
- assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ # TODO: switch this assertion around once we changed #tables to not return views.
+ ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
end
def test_column_definitions
@@ -167,7 +168,7 @@ class UpdateableViewTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books"
+ @connection.execute "DROP VIEW printed_books" if @connection.view_exists? "printed_books"
end
def test_update_record
@@ -209,8 +210,7 @@ class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase
end
def drop_view(name)
- @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name
-
+ @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.view_exists? name
end
end
end
diff --git a/activerecord/test/fixtures/content.yml b/activerecord/test/fixtures/content.yml
new file mode 100644
index 0000000000..0d12ee03dc
--- /dev/null
+++ b/activerecord/test/fixtures/content.yml
@@ -0,0 +1,3 @@
+content:
+ id: 1
+ title: How to use Rails
diff --git a/activerecord/test/fixtures/content_positions.yml b/activerecord/test/fixtures/content_positions.yml
new file mode 100644
index 0000000000..9e85773f8e
--- /dev/null
+++ b/activerecord/test/fixtures/content_positions.yml
@@ -0,0 +1,3 @@
+content_positions:
+ id: 1
+ content_id: 1
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index a6e83fe353..c1e491e5c5 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,6 +1,7 @@
class Bulb < ActiveRecord::Base
default_scope { where(:name => 'defaulty') }
belongs_to :car, :touch => true
+ scope :awesome, -> { where(frickinawesome: true) }
attr_reader :scope_after_initialize, :attributes_after_initialize
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 81263b79d1..778c22b1f6 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -4,6 +4,7 @@ class Car < ActiveRecord::Base
has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy
has_many :failed_bulbs, class_name: 'FailedBulb', dependent: :destroy
has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb"
+ has_many :awesome_bulbs, -> { awesome }, class_name: "Bulb"
has_one :bulb
diff --git a/activerecord/test/models/content.rb b/activerecord/test/models/content.rb
new file mode 100644
index 0000000000..140e1dfc78
--- /dev/null
+++ b/activerecord/test/models/content.rb
@@ -0,0 +1,40 @@
+class Content < ActiveRecord::Base
+ self.table_name = 'content'
+ has_one :content_position, dependent: :destroy
+
+ def self.destroyed_ids
+ @destroyed_ids ||= []
+ end
+
+ before_destroy do |object|
+ Content.destroyed_ids << object.id
+ end
+end
+
+class ContentWhichRequiresTwoDestroyCalls < ActiveRecord::Base
+ self.table_name = 'content'
+ has_one :content_position, foreign_key: 'content_id', dependent: :destroy
+
+ after_initialize do
+ @destroy_count = 0
+ end
+
+ before_destroy do
+ @destroy_count += 1
+ if @destroy_count == 1
+ throw :abort
+ end
+ end
+end
+
+class ContentPosition < ActiveRecord::Base
+ belongs_to :content, dependent: :destroy
+
+ def self.destroyed_ids
+ @destroyed_ids ||= []
+ end
+
+ before_destroy do |object|
+ ContentPosition.destroyed_ids << object.id
+ end
+end
diff --git a/activerecord/test/models/guitar.rb b/activerecord/test/models/guitar.rb
new file mode 100644
index 0000000000..cd068ff53d
--- /dev/null
+++ b/activerecord/test/models/guitar.rb
@@ -0,0 +1,4 @@
+class Guitar < ActiveRecord::Base
+ has_many :tuning_pegs, index_errors: true
+ accepts_nested_attributes_for :tuning_pegs
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 81a18188d4..23cebe2602 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -185,6 +185,7 @@ class SubStiPost < StiPost
end
class FirstPost < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { where(:id => 1) }
@@ -193,6 +194,7 @@ class FirstPost < ActiveRecord::Base
end
class PostWithDefaultInclude < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { includes(:comments) }
has_many :comments, :foreign_key => :post_id
@@ -204,6 +206,7 @@ class PostWithSpecialCategorization < Post
end
class PostWithDefaultScope < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { order(:title) }
end
@@ -225,11 +228,13 @@ class PostWithIncludesDefaultScope < ActiveRecord::Base
end
class SpecialPostWithDefaultScope < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
end
class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id
@@ -239,6 +244,7 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
end
class PostWithAfterCreateCallback < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comments, foreign_key: :post_id
@@ -248,6 +254,7 @@ class PostWithAfterCreateCallback < ActiveRecord::Base
end
class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base
+ self.inheritance_column = :disabled
self.table_name = 'posts'
has_many :comment_with_default_scope_references_associations, foreign_key: :post_id
has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index b034e0e267..efa8246f1e 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -15,6 +15,14 @@ class Project < ActiveRecord::Base
belongs_to :firm
has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
+ begin
+ previous_value, ActiveRecord::Base.belongs_to_required_by_default =
+ ActiveRecord::Base.belongs_to_required_by_default, true
+ has_and_belongs_to_many :developers_required_by_default, class_name: "Developer"
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = previous_value
+ end
+
attr_accessor :developers_log
after_initialize :set_developers_log
diff --git a/activerecord/test/models/tuning_peg.rb b/activerecord/test/models/tuning_peg.rb
new file mode 100644
index 0000000000..1252d6dc1d
--- /dev/null
+++ b/activerecord/test/models/tuning_peg.rb
@@ -0,0 +1,4 @@
+class TuningPeg < ActiveRecord::Base
+ belongs_to :guitar
+ validates_numericality_of :pitch
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 690ab1af4b..99098017d7 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -114,7 +114,7 @@ ActiveRecord::Schema.define do
create_table :bulbs, force: true do |t|
t.integer :car_id
t.string :name
- t.boolean :frickinawesome
+ t.boolean :frickinawesome, default: false
t.string :color
end
@@ -207,6 +207,14 @@ ActiveRecord::Schema.define do
add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
add_index :companies, :name, name: 'company_name_index', using: :btree
+ create_table :content, force: true do |t|
+ t.string :title
+ end
+
+ create_table :content_positions, force: true do |t|
+ t.integer :content_id
+ end
+
create_table :vegetables, force: true do |t|
t.string :name
t.integer :seller_id
@@ -348,6 +356,10 @@ ActiveRecord::Schema.define do
t.column :key, :string
end
+ create_table :guitar, force: true do |t|
+ t.string :color
+ end
+
create_table :inept_wizards, force: true do |t|
t.column :name, :string, null: false
t.column :city, :string, null: false
@@ -851,6 +863,11 @@ ActiveRecord::Schema.define do
t.belongs_to :ship
end
+ create_table :tuning_pegs, force: true do |t|
+ t.integer :guitar_id
+ t.float :pitch
+ end
+
create_table :tyres, force: true do |t|
t.integer :car_id
end
diff --git a/activerecord/test/support/schema_dumping_helper.rb b/activerecord/test/support/schema_dumping_helper.rb
index 2d1651454d..666c1b6a14 100644
--- a/activerecord/test/support/schema_dumping_helper.rb
+++ b/activerecord/test/support/schema_dumping_helper.rb
@@ -1,7 +1,7 @@
module SchemaDumpingHelper
def dump_table_schema(table, connection = ActiveRecord::Base.connection)
old_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
- ActiveRecord::SchemaDumper.ignore_tables = connection.tables - [table]
+ ActiveRecord::SchemaDumper.ignore_tables = connection.data_sources - [table]
stream = StringIO.new
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
stream.string
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 3705fc57fc..5a73ce9b28 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,34 @@
+* Implements an evented file system monitor to asynchronously detect changes
+ in the application source code, routes, locales, etc.
+
+ To opt-in load the [listen](https://github.com/guard/listen) gem in `Gemfile`:
+
+ group :development do
+ gem 'listen', '~> 3.0.4'
+ end
+
+ *Puneet Agarwal* and *Xavier Noria*
+
+* Added `Time.days_in_year` to return the number of days in the given year, or the
+ current year if no argument is provided.
+
+ *Jon Pascoe*
+
+* Updated `parameterize` to preserve the case of a string, optionally.
+
+ Example:
+
+ parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
+
+ *Swaathi Kakarla*
+
+* `HashWithIndifferentAccess.new` respects the default value or proc on objects
+ that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`.
+ All calls to `.new_from_hash_copying_default` are replaced with `.new`.
+
+ *Gordon Chan*
+
* Change Integer#year to return a Fixnum instead of a Float to improve
consistency.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 93878518d7..32e28c0212 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -24,6 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre3', '< 2.0.0'
+ s.add_dependency 'concurrent-ruby', '~> 1.0'
s.add_dependency 'method_source'
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 63277a65b4..3a2a7d28cb 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -34,6 +34,7 @@ module ActiveSupport
autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
+ autoload :FileEventedUpdateChecker
autoload :LogSubscriber
autoload :Notifications
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 3996f583c2..402a35e198 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -275,7 +275,7 @@ module ActiveSupport
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
@@ -302,7 +302,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
if entry
@@ -334,7 +334,7 @@ module ActiveSupport
instrument_multi(:read, names, options) do |payload|
results = {}
names.each do |name|
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
entry = read_entry(key, options)
if entry
if entry.expired?
@@ -386,7 +386,7 @@ module ActiveSupport
instrument(:write, name, options) do
entry = Entry.new(value, options)
- write_entry(namespaced_key(name, options), entry, options)
+ write_entry(normalize_key(name, options), entry, options)
end
end
@@ -397,7 +397,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:delete, name) do
- delete_entry(namespaced_key(name, options), options)
+ delete_entry(normalize_key(name, options), options)
end
end
@@ -408,7 +408,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:exist?, name) do
- entry = read_entry(namespaced_key(name, options), options)
+ entry = read_entry(normalize_key(name, options), options)
(entry && !entry.expired?) || false
end
end
@@ -529,16 +529,17 @@ module ActiveSupport
# Prefix a key with the namespace. Namespace and key will be delimited
# with a colon.
- def namespaced_key(key, options)
+ def normalize_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
key = "#{prefix}:#{key}" if prefix
key
end
+ alias namespaced_key normalize_key
def instrument(operation, key, options = nil)
- log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" }
+ log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index b7da30123a..9488b95214 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -10,6 +10,7 @@ module ActiveSupport
# FileStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class FileStore < Store
+ prepend Strategy::LocalCache
attr_reader :cache_path
DIR_FORMATTER = "%03X"
@@ -20,7 +21,6 @@ module ActiveSupport
def initialize(cache_path, options = nil)
super(options)
@cache_path = cache_path.to_s
- extend Strategy::LocalCache
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
@@ -60,7 +60,7 @@ module ActiveSupport
matcher = key_matcher(matcher, options)
search_dir(cache_path) do |path|
key = file_path_key(path)
- delete_entry(key, options) if key.match(matcher)
+ delete_entry(path, options) if key.match(matcher)
end
end
end
@@ -68,9 +68,8 @@ module ActiveSupport
protected
def read_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
- File.open(file_name) { |f| Marshal.load(f) }
+ if File.exist?(key)
+ File.open(key) { |f| Marshal.load(f) }
end
rescue => e
logger.error("FileStoreError (#{e}): #{e.message}") if logger
@@ -78,23 +77,21 @@ module ActiveSupport
end
def write_entry(key, entry, options)
- file_name = key_file_path(key)
- return false if options[:unless_exist] && File.exist?(file_name)
- ensure_cache_path(File.dirname(file_name))
- File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
+ return false if options[:unless_exist] && File.exist?(key)
+ ensure_cache_path(File.dirname(key))
+ File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
true
end
def delete_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
+ if File.exist?(key)
begin
- File.delete(file_name)
- delete_empty_directories(File.dirname(file_name))
+ File.delete(key)
+ delete_empty_directories(File.dirname(key))
true
rescue => e
# Just in case the error was caused by another process deleting the file first.
- raise e if File.exist?(file_name)
+ raise e if File.exist?(key)
false
end
end
@@ -118,7 +115,8 @@ module ActiveSupport
end
# Translate a key into a file path.
- def key_file_path(key)
+ def normalize_key(key, options)
+ key = super
fname = URI.encode_www_form_component(key)
if fname.size > FILEPATH_MAX_SIZE
@@ -175,7 +173,7 @@ module ActiveSupport
# Modifies the amount of an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def modify_value(name, amount, options)
- file_name = key_file_path(namespaced_key(name, options))
+ file_name = normalize_key(name, options)
lock_file(file_name) do
options = merged_options(options)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 47133bf550..5eaf68a244 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -24,6 +24,31 @@ module ActiveSupport
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class MemCacheStore < Store
+ # Provide support for raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ protected
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options) # :nodoc:
+ retval = super
+ if options[:raw] && local_cache && retval
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ local_cache.write_entry(key, raw_entry, options)
+ end
+ retval
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
# Creates a new Dalli::Client instance with specified addresses and options.
@@ -63,9 +88,6 @@ module ActiveSupport
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
end
-
- extend Strategy::LocalCache
- extend LocalCacheWithRaw
end
# Reads multiple values from the cache using a single call to the
@@ -75,7 +97,7 @@ module ActiveSupport
options = merged_options(options)
instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
+ keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
values = {}
raw_values.each do |key, value|
@@ -93,7 +115,7 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:increment, name, :amount => amount) do
- @data.incr(escape_key(namespaced_key(name, options)), amount)
+ @data.incr(normalize_key(name, options), amount)
end
rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
@@ -107,7 +129,7 @@ module ActiveSupport
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:decrement, name, :amount => amount) do
- @data.decr(escape_key(namespaced_key(name, options)), amount)
+ @data.decr(normalize_key(name, options), amount)
end
rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
@@ -131,7 +153,7 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(escape_key(key), options))
+ deserialize_entry(@data.get(key, options))
rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
nil
@@ -146,7 +168,7 @@ module ActiveSupport
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
- @data.send(method, escape_key(key), value, expires_in, options)
+ @data.send(method, key, value, expires_in, options)
rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
false
@@ -154,7 +176,7 @@ module ActiveSupport
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- @data.delete(escape_key(key))
+ @data.delete(key)
rescue Dalli::DalliError => e
logger.error("DalliError (#{e}): #{e.message}") if logger
false
@@ -165,8 +187,8 @@ module ActiveSupport
# Memcache keys are binaries. So we need to force their encoding to binary
# before applying the regular expression to ensure we are escaping all
# characters properly.
- def escape_key(key)
- key = key.to_s.dup
+ def normalize_key(key, options)
+ key = super.dup
key = key.force_encoding(Encoding::ASCII_8BIT)
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
@@ -181,28 +203,6 @@ module ActiveSupport
nil
end
end
-
- # Provide support for raw values in the local cache strategy.
- module LocalCacheWithRaw # :nodoc:
- protected
- def read_entry(key, options)
- entry = super
- if options[:raw] && local_cache && entry
- entry = deserialize_entry(entry.value)
- end
- entry
- end
-
- def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
- raw_entry = Entry.new(entry.value.to_s)
- raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
- end
- retval
- end
- end
end
end
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 90bb2c38c3..896c28ad8b 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -75,30 +75,12 @@ module ActiveSupport
# Increment an integer value in the cache.
def increment(name, amount = 1, options = nil)
- synchronize do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i + amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, amount, options)
end
# Decrement an integer value in the cache.
def decrement(name, amount = 1, options = nil)
- synchronize do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i - amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, -amount, options)
end
def delete_matched(matcher, options = nil)
@@ -167,6 +149,19 @@ module ActiveSupport
!!entry
end
end
+
+ private
+
+ def modify_value(name, amount, options)
+ synchronize do
+ options = merged_options(options)
+ if num = read(name, options)
+ num = num.to_i + amount
+ write(name, num, options)
+ num
+ end
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
index 4427eaafcd..0564ce5312 100644
--- a/activesupport/lib/active_support/cache/null_store.rb
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -8,10 +8,7 @@ module ActiveSupport
# be cached inside blocks that utilize this strategy. See
# ActiveSupport::Cache::Strategy::LocalCache for more details.
class NullStore < Store
- def initialize(options = nil)
- super(options)
- extend Strategy::LocalCache
- end
+ prepend Strategy::LocalCache
def clear(options = nil)
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index fe5bc82c30..46d5c6879d 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -60,6 +60,10 @@ module ActiveSupport
def delete_entry(key, options)
!!@data.delete(key)
end
+
+ def fetch_entry(key, options = nil) # :nodoc:
+ @data.fetch(key) { @data[key] = yield }
+ end
end
# Use a local cache for the duration of block.
@@ -75,36 +79,35 @@ module ActiveSupport
end
def clear(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def cleanup(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def increment(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ set_cache_value(name, value, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ set_cache_value(name, value, options)
value
end
protected
def read_entry(key, options) # :nodoc:
- if local_cache
- entry = local_cache.read_entry(key, options)
- unless entry
- entry = super
- local_cache.write_entry(key, entry, options)
- end
- entry
+ if cache = local_cache
+ cache.fetch_entry(key) { super }
else
super
end
@@ -120,14 +123,14 @@ module ActiveSupport
super
end
- def set_cache_value(value, name, amount, options) # :nodoc:
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
+ def set_cache_value(name, value, options) # :nodoc:
+ name = normalize_key(name, options)
+ cache = local_cache
+ cache.mute do
+ if value
+ cache.write(name, value, options)
+ else
+ cache.delete(name, options)
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
index 7b8df0df04..4abe5ece6f 100644
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ b/activesupport/lib/active_support/concurrency/latch.rb
@@ -1,4 +1,4 @@
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
module ActiveSupport
module Concurrency
@@ -8,7 +8,7 @@ module ActiveSupport
ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
super(count)
end
-
+
alias_method :release, :count_down
def await
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 28cb3e2a3b..6df7b4121b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -6,7 +6,7 @@ class Hash
#
# { a: 1 }.with_indifferent_access['a'] # => 1
def with_indifferent_access
- ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
+ ActiveSupport::HashWithIndifferentAccess.new(self)
end
# Called when object is nested under an object that receives
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 07a282e8b6..8b2366c4b3 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -10,7 +10,7 @@ class Hash
#
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
- return enum_for(:transform_keys) unless block_given?
+ return enum_for(:transform_keys) { size } unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
@@ -21,7 +21,7 @@ class Hash
# Destructively converts all keys using the +block+ operations.
# Same as +transform_keys+ but modifies +self+.
def transform_keys!
- return enum_for(:transform_keys!) unless block_given?
+ return enum_for(:transform_keys!) { size } unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index 9ddb838774..7d507ac998 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -9,7 +9,7 @@ class Hash
#
# { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
def transform_values
- return enum_for(:transform_values) unless block_given?
+ return enum_for(:transform_values) { size } unless block_given?
return {} if empty?
result = self.class.new
each do |key, value|
@@ -21,7 +21,7 @@ class Hash
# Destructively converts all values using the +block+ operations.
# Same as +transform_values+ but modifies +self+.
def transform_values!
- return enum_for(:transform_values!) unless block_given?
+ return enum_for(:transform_values!) { size } unless block_given?
each do |key, value|
self[key] = yield(value)
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index bf175a8a70..124f90dc0f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/array/extract_options'
# attributes.
class Module
# Defines a class attribute and creates a class and instance reader methods.
- # The underlying the class variable is set to +nil+, if it is not previously
+ # The underlying class variable is set to +nil+, if it is not previously
# defined.
#
# module HairColors
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index b2e713077c..cc71b8155d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -164,8 +164,26 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
- def parameterize(sep = '-'.freeze)
- ActiveSupport::Inflector.parameterize(self, sep)
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # class Person
+ # def to_param
+ # "#{id}-#{name.parameterize(preserve_case: true)}"
+ # end
+ # end
+ #
+ # @person = Person.find(1)
+ # # => #<Person id: 1, name: "Donald E. Knuth">
+ #
+ # <%= link_to(@person.name, person_path) %>
+ # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
+ def parameterize(sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
+ ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
end
# Creates the name of a table like Rails does for models to table names. This method
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 82e003fc3b..675db8a36b 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -26,6 +26,12 @@ class Time
end
end
+ # Returns the number of days in the given year.
+ # If no year is specified, it will use the current year.
+ def days_in_year(year = current.year)
+ days_in_month(2, year) + 337
+ end
+
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
def current
::Time.zone ? ::Time.zone.now : ::Time.now
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 16b726bcba..af18ff746f 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,6 +1,6 @@
require 'set'
require 'thread'
-require 'concurrent'
+require 'concurrent/map'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -25,21 +25,21 @@ module ActiveSupport #:nodoc:
# :doc:
# Execute the supplied block without interference from any
- # concurrent loads
+ # concurrent loads.
def self.run_interlock
Dependencies.interlock.running { yield }
end
# Execute the supplied block while holding an exclusive lock,
# preventing any other thread from being inside a #run_interlock
- # block at the same time
+ # block at the same time.
def self.load_interlock
Dependencies.interlock.loading { yield }
end
# Execute the supplied block while holding an exclusive lock,
# preventing any other thread from being inside a #run_interlock
- # block at the same time
+ # block at the same time.
def self.unload_interlock
Dependencies.interlock.unloading { yield }
end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index bbe25c9260..f89fc0fe14 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -83,7 +83,7 @@ module ActiveSupport
rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
offending_line = callstack.find { |frame|
- !frame.absolute_path.start_with?(rails_gem_root)
+ frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root)
} || callstack.first
[offending_line.path, offending_line.lineno, offending_line.label]
end
diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb
new file mode 100644
index 0000000000..638458c6ce
--- /dev/null
+++ b/activesupport/lib/active_support/file_evented_update_checker.rb
@@ -0,0 +1,147 @@
+require 'listen'
+require 'set'
+require 'pathname'
+
+module ActiveSupport
+ class FileEventedUpdateChecker #:nodoc: all
+ def initialize(files, dirs = {}, &block)
+ @ph = PathHelper.new
+ @files = files.map { |f| @ph.xpath(f) }.to_set
+
+ @dirs = {}
+ dirs.each do |dir, exts|
+ @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
+ end
+
+ @block = block
+ @updated = false
+ @lcsp = @ph.longest_common_subpath(@dirs.keys)
+
+ if (dtw = directories_to_watch).any?
+ Listen.to(*dtw, &method(:changed)).start
+ end
+ end
+
+ def updated?
+ @updated
+ end
+
+ def execute
+ @block.call
+ ensure
+ @updated = false
+ end
+
+ def execute_if_updated
+ if updated?
+ execute
+ true
+ end
+ end
+
+ private
+
+ def changed(modified, added, removed)
+ unless updated?
+ @updated = (modified + added + removed).any? { |f| watching?(f) }
+ end
+ end
+
+ def watching?(file)
+ file = @ph.xpath(file)
+
+ if @files.member?(file)
+ true
+ elsif file.directory?
+ false
+ else
+ ext = @ph.normalize_extension(file.extname)
+
+ file.dirname.ascend do |dir|
+ if @dirs.fetch(dir, []).include?(ext)
+ break true
+ elsif dir == @lcsp || dir.root?
+ break false
+ end
+ end
+ end
+ end
+
+ def directories_to_watch
+ dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
+ dtw.compact!
+ dtw.uniq!
+
+ @ph.filter_out_descendants(dtw)
+ end
+
+ class PathHelper
+ using Module.new {
+ refine Pathname do
+ def ascendant_of?(other)
+ self != other && other.ascend do |ascendant|
+ break true if self == ascendant
+ end
+ end
+ end
+ }
+
+ def xpath(path)
+ Pathname.new(path).expand_path
+ end
+
+ def normalize_extension(ext)
+ ext.to_s.sub(/\A\./, '')
+ end
+
+ # Given a collection of Pathname objects returns the longest subpath
+ # common to all of them, or +nil+ if there is none.
+ def longest_common_subpath(paths)
+ return if paths.empty?
+
+ lcsp = Pathname.new(paths[0])
+
+ paths[1..-1].each do |path|
+ until lcsp.ascendant_of?(path)
+ if lcsp.root?
+ # If we get here a root directory is not an ascendant of path.
+ # This may happen if there are paths in different drives on
+ # Windows.
+ return
+ else
+ lcsp = lcsp.parent
+ end
+ end
+ end
+
+ lcsp
+ end
+
+ # Returns the deepest existing ascendant, which could be the argument itself.
+ def existing_parent(dir)
+ dir.ascend do |ascendant|
+ break ascendant if ascendant.directory?
+ end
+ end
+
+ # Filters out directories which are descendants of others in the collection (stable).
+ def filter_out_descendants(dirs)
+ return dirs if dirs.length < 2
+
+ dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
+ descendants = []
+
+ until dirs_sorted_by_nparts.empty?
+ dir = dirs_sorted_by_nparts.shift
+
+ dirs_sorted_by_nparts.reject! do |possible_descendant|
+ dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
+ end
+ end
+
+ # Array#- preserves order.
+ dirs - descendants
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 78b627c286..1fa9335080 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -35,7 +35,7 @@ module ActiveSupport
# This method must also receive a block that will be called once a path
# changes. The array of files and list of directories cannot be changed
# after FileUpdateChecker has been initialized.
- def initialize(files, dirs={}, &block)
+ def initialize(files, dirs = {}, &block)
@files = files.freeze
@glob = compile_glob(dirs)
@block = block
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 7068f09d87..ece68bbcb6 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -1,5 +1,5 @@
module ActiveSupport
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
def self.gem_version
Gem::Version.new VERSION::STRING
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 0371f760b0..4ff35a45a1 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -59,6 +59,10 @@ module ActiveSupport
if constructor.respond_to?(:to_hash)
super()
update(constructor)
+
+ hash = constructor.to_hash
+ self.default = hash.default if hash.default
+ self.default_proc = hash.default_proc if hash.default_proc
else
super(constructor)
end
@@ -73,11 +77,12 @@ module ActiveSupport
end
def self.new_from_hash_copying_default(hash)
- hash = hash.to_hash
- new(hash).tap do |new_hash|
- new_hash.default = hash.default
- new_hash.default_proc = hash.default_proc if hash.default_proc
- end
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
+ has been deprecated, and will be removed in Rails 5.1. The behavior of
+ this method is now identical to the behavior of `.new`.
+ MSG
+ new(hash)
end
def self.[](*args)
@@ -206,7 +211,7 @@ module ActiveSupport
# hash['a'] = nil
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
def reverse_merge(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
@@ -219,7 +224,7 @@ module ActiveSupport
# h = { "a" => 100, "b" => 200 }
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
def replace(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Removes the specified key from the hash.
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 6775eec34b..82aacf3b24 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -56,7 +56,7 @@ module I18n
I18n.enforce_available_locales = enforce_available_locales
directories = watched_dirs_with_extensions(reloadable_paths)
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do
+ reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
I18n.load_path.keep_if { |p| File.exist?(p) }
I18n.load_path |= reloadable_paths.map(&:existent).flatten
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index c3907e9c22..f3e52b48ac 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,4 +1,4 @@
-require 'concurrent'
+require 'concurrent/map'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 103207fb63..871cfb8a72 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -68,29 +68,44 @@ module ActiveSupport
#
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
# parameterize("^trés|Jolie-- ") # => "tres-jolie"
- def parameterize(string, sep = '-')
+ #
+ # To use a custom separator, override the `separator` argument.
+ #
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie"
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
+ # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
+ #
+ def parameterize(string, sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
# Replace accented chars with their ASCII equivalents.
parameterized_string = transliterate(string)
# Turn unwanted chars into the separator.
- parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
+ parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
- unless sep.nil? || sep.empty?
- if sep == "-".freeze
+ unless separator.nil? || separator.empty?
+ if separator == "-".freeze
re_duplicate_separator = /-{2,}/
re_leading_trailing_separator = /^-|-$/i
else
- re_sep = Regexp.escape(sep)
+ re_sep = Regexp.escape(separator)
re_duplicate_separator = /#{re_sep}{2,}/
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
end
# No more than one of the separator in a row.
- parameterized_string.gsub!(re_duplicate_separator, sep)
+ parameterized_string.gsub!(re_duplicate_separator, separator)
# Remove leading/trailing separator.
parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
-
- parameterized_string.downcase!
+
+ parameterized_string.downcase! unless preserve_case
parameterized_string
end
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 6bc3db6ec6..7f73f9ddfc 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,8 +1,8 @@
-require 'concurrent'
+require 'concurrent/map'
require 'openssl'
module ActiveSupport
- # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
+ # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2.
# It can be used to derive a number of keys for various purposes from a given secret.
# This lets Rails applications have a single secure secret, but avoid reusing that
# key in multiple incompatible contexts.
@@ -24,7 +24,7 @@ module ActiveSupport
# CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
# re-executing the key generation process when it's called using the same salt and
- # key_size
+ # key_size.
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index cbc20c103d..588ed67c81 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -10,8 +10,7 @@ module ActiveSupport
# class SyncLogSubscriberTest < ActiveSupport::TestCase
# include ActiveSupport::LogSubscriber::TestHelper
#
- # def setup
- # super
+ # setup do
# ActiveRecord::LogSubscriber.attach_to(:active_record)
# end
#
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index c82a13511e..2dde01c844 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -34,8 +34,8 @@ module ActiveSupport
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
# bits. If you are using a user-entered secret, you can generate a suitable
- # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
- # similar.
+ # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
+ # derivation function.
#
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 71354dd15f..7798c7ec60 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,5 +1,5 @@
require 'mutex_m'
-require 'concurrent'
+require 'concurrent/map'
module ActiveSupport
module Notifications
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 504f96961a..248521e677 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -115,8 +115,8 @@ module ActiveSupport
# number_to_percentage(100, precision: 0) # => 100%
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
- # number_to_percentage(1000, locale: :fr) # => 1 000,000%
- # number_to_percentage:(1000, precision: nil) # => 1000%
+ # number_to_percentage(1000, locale: :fr) # => 1000,000%
+ # number_to_percentage(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
# number_to_percentage(100, format: '%n %') # => 100.000 %
def number_to_percentage(number, options = {})
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 45864990ce..53a55bd986 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -20,7 +20,7 @@ module ActiveSupport
# To raise an exception when the value is blank, append a
# bang to the key name, like:
#
- # h.dog! # => raises KeyError
+ # h.dog! # => raises KeyError: key not found: :dog
#
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 506dd950cb..a909a65bb6 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/delegation'
+
module ActiveSupport
# This module is used to encapsulate access to thread local variables.
#
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index ae8c15d8bf..29305e0082 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/blank'
module ActiveSupport
module Testing
module Assertions
- # Assert that an expression is not truthy. Passes if <tt>object</tt> is
+ # Asserts that an expression is not truthy. Passes if <tt>object</tt> is
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
# like <tt>if foo</tt>.
#
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 910c1f91a5..79cc748cf5 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -284,8 +284,8 @@ module ActiveSupport
# the current object's time and the +other+ time.
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
- # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
- # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00
#
# If subtracting a Duration of variable length (i.e., years, months, days),
# move backward from #time, otherwise move backward from #utc, for accuracy
@@ -294,8 +294,8 @@ module ActiveSupport
# For instance, a time - 24.hours will go subtract exactly 24 hours, while a
# time - 1.day will subtract 23-25 hours, depending on the day.
#
- # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
- # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
+ # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def -(other)
if other.acts_like?(:time)
to_time - other.to_time
@@ -307,10 +307,48 @@ module ActiveSupport
end
end
+ # Subtracts an interval of time from the current object's time and returns
+ # the result as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00
+ #
+ # If we're subtracting a Duration of variable length (i.e., years, months,
+ # days), move backward from #time, otherwise move backward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours,
+ # while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on
+ # the day.
+ #
+ # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def ago(other)
since(-other)
end
+ # Uses Date to provide precise Time calculations for years, months, and days
+ # according to the proleptic Gregorian calendar. The result is returned as a
+ # new TimeWithZone object.
+ #
+ # The +options+ parameter takes a hash with any of these keys:
+ # <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>,
+ # <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>.
+ #
+ # If advancing by a value of variable length (i.e., years, weeks, months,
+ # days), move forward from #time, otherwise move forward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00
+ # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00
+ # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00
+ # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00
+ # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00
+ # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00
def advance(options)
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
# otherwise advance from #utc, for accuracy when moving across DST boundaries
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 9f4bb6762d..6404f65612 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,5 +1,5 @@
require 'tzinfo'
-require 'concurrent'
+require 'concurrent/map'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index fb52613a23..3f9682231f 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -416,7 +416,7 @@ module CacheStoreBehavior
def test_race_condition_protection_skipped_if_not_defined
@cache.write('foo', 'bar')
- time = @cache.send(:read_entry, 'foo', {}).expires_at
+ time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at
Time.stub(:now, Time.at(time)) do
result = @cache.fetch('foo') do
@@ -625,6 +625,21 @@ module LocalCacheBehavior
end
end
+ def test_local_cache_of_read_nil
+ @cache.with_local_cache do
+ assert_equal nil, @cache.read('foo')
+ @cache.send(:bypass_local_cache) { @cache.write 'foo', 'bar' }
+ assert_equal nil, @cache.read('foo')
+ end
+ end
+
+ def test_local_cache_fetch
+ @cache.with_local_cache do
+ @cache.send(:local_cache).write 'foo', 'bar'
+ assert_equal 'bar', @cache.send(:local_cache).fetch('foo')
+ end
+ end
+
def test_local_cache_of_write_nil
@cache.with_local_cache do
assert @cache.write('foo', nil)
@@ -768,13 +783,13 @@ class FileStoreTest < ActiveSupport::TestCase
end
def test_key_transformation
- key = @cache.send(:key_file_path, "views/index?id=1")
+ key = @cache.send(:normalize_key, "views/index?id=1", {})
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
end
def test_key_transformation_with_pathname
FileUtils.touch(File.join(cache_dir, "foo"))
- key = @cache_with_pathname.send(:key_file_path, "views/index?id=1")
+ key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
end
@@ -782,7 +797,7 @@ class FileStoreTest < ActiveSupport::TestCase
# remain valid
def test_filename_max_size
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
- path = @cache.send(:key_file_path, key)
+ path = @cache.send(:normalize_key, key, {})
Dir::Tmpname.create(path) do |tmpname, n, opts|
assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}"
end
@@ -792,7 +807,7 @@ class FileStoreTest < ActiveSupport::TestCase
# If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
def test_key_transformation_max_filename_size
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
- path = @cache.send(:key_file_path, key)
+ path = @cache.send(:normalize_key, key, {})
assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}
assert_equal 'B', File.basename(path)
end
@@ -1086,6 +1101,19 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
assert @buffer.string.present?
end
+ def test_log_with_string_namespace
+ @cache.fetch('foo', {namespace: 'string_namespace'}) { 'bar' }
+ assert_match %r{string_namespace:foo}, @buffer.string
+ end
+
+ def test_log_with_proc_namespace
+ proc = Proc.new do
+ "proc_namespace"
+ end
+ @cache.fetch('foo', {:namespace => proc}) { 'bar' }
+ assert_match %r{proc_namespace:foo}, @buffer.string
+ end
+
def test_mute_logging
@cache.mute { @cache.fetch('foo') { 'bar' } }
assert @buffer.string.blank?
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index 5a0b99e22c..99af274614 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -18,15 +18,17 @@ class TransformKeysTest < ActiveSupport::TestCase
assert_same original, mapped
end
- test "transform_keys returns an Enumerator if no block is given" do
+ test "transform_keys returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_keys
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
- test "transform_keys! returns an Enumerator if no block is given" do
+ test "transform_keys! returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_keys!
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
index 7c33227dc0..114022fbaf 100644
--- a/activesupport/test/core_ext/hash/transform_values_test.rb
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -47,15 +47,17 @@ class TransformValuesTest < ActiveSupport::TestCase
assert_nil mapped[:b]
end
- test "transform_values returns an Enumerator if no block is given" do
+ test "transform_values returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_values
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
- test "transform_values! returns an Enumerator if no block is given" do
+ test "transform_values! returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_values!
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 4624338df1..2119352df0 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1042,7 +1042,25 @@ class HashExtTest < ActiveSupport::TestCase
hash = Hash.new
hash.default_proc = proc { |h, k| raise "walrus" }
- assert_nothing_raised { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
+ assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(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
end
@@ -1595,7 +1613,6 @@ class HashToXmlTest < ActiveSupport::TestCase
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
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 9cc7bb1a77..2e69816364 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -143,15 +143,49 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_string_parameterized_normal_preserve_case
+ StringToParameterizedPreserveCase.each do |normal, slugged|
+ assert_equal(normal.parameterize(preserve_case: true), slugged)
+ end
+ end
+
def test_string_parameterized_no_separator
StringToParameterizeWithNoSeparator.each do |normal, slugged|
- assert_equal(normal.parameterize(''), slugged)
+ assert_equal(normal.parameterize(separator: ''), slugged)
+ end
+ end
+
+ def test_string_parameterized_no_separator_deprecated
+ StringToParameterizeWithNoSeparator.each do |normal, slugged|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do
+ assert_equal(normal.parameterize(''), slugged)
+ end
+ end
+ end
+
+ def test_string_parameterized_no_separator_preserve_case
+ StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged|
+ assert_equal(normal.parameterize(separator: '', preserve_case: true), slugged)
end
end
def test_string_parameterized_underscore
StringToParameterizeWithUnderscore.each do |normal, slugged|
- assert_equal(normal.parameterize('_'), slugged)
+ assert_equal(normal.parameterize(separator: '_'), slugged)
+ end
+ end
+
+ def test_string_parameterized_underscore_deprecated
+ StringToParameterizeWithUnderscore.each do |normal, slugged|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
+ assert_equal(normal.parameterize('_'), slugged)
+ end
+ end
+ end
+
+ def test_string_parameterized_underscore_preserve_case
+ StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged|
+ assert_equal(normal.parameterize(separator: '_', preserve_case: true), slugged)
end
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 2d0fb70a6b..e45df63fd5 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -617,6 +617,25 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_days_in_year_with_year
+ assert_equal 365, Time.days_in_year(2005)
+ assert_equal 366, Time.days_in_year(2004)
+ assert_equal 366, Time.days_in_year(2000)
+ assert_equal 365, Time.days_in_year(1900)
+ end
+
+ def test_days_in_year_in_common_year_without_year_arg
+ Time.stub(:now, Time.utc(2007)) do
+ assert_equal 365, Time.days_in_year
+ end
+ end
+
+ def test_days_in_year_in_leap_year_without_year_arg
+ Time.stub(:now, Time.utc(2008)) do
+ assert_equal 366, Time.days_in_year
+ end
+ end
+
def test_last_month_on_31st
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
end
diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/file_evented_update_checker_test.rb
new file mode 100644
index 0000000000..071449d399
--- /dev/null
+++ b/activesupport/test/file_evented_update_checker_test.rb
@@ -0,0 +1,150 @@
+require 'abstract_unit'
+require 'pathname'
+require 'file_update_checker_shared_tests'
+
+class FileEventedUpdateCheckerTest < ActiveSupport::TestCase
+ include FileUpdateCheckerSharedTests
+
+ def new_checker(files = [], dirs = {}, &block)
+ ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block).tap do
+ wait
+ end
+ end
+
+ def teardown
+ super
+ Listen.stop
+ end
+
+ def wait
+ sleep 1
+ end
+
+ def touch(files)
+ super
+ wait # wait for the events to fire
+ end
+
+ def rm_f(files)
+ super
+ wait
+ end
+end
+
+class FileEventedUpdateCheckerPathHelperTest < ActiveSupport::TestCase
+ def pn(path)
+ Pathname.new(path)
+ end
+
+ setup do
+ @ph = ActiveSupport::FileEventedUpdateChecker::PathHelper.new
+ end
+
+ test '#xpath returns the expanded path as a Pathname object' do
+ assert_equal pn(__FILE__).expand_path, @ph.xpath(__FILE__)
+ end
+
+ test '#normalize_extension returns a bare extension as is' do
+ assert_equal 'rb', @ph.normalize_extension('rb')
+ end
+
+ test '#normalize_extension removes a leading dot' do
+ assert_equal 'rb', @ph.normalize_extension('.rb')
+ end
+
+ test '#normalize_extension supports symbols' do
+ assert_equal 'rb', @ph.normalize_extension(:rb)
+ end
+
+ test '#longest_common_subpath finds the longest common subpath, if there is one' do
+ paths = %w(
+ /foo/bar
+ /foo/baz
+ /foo/bar/baz/woo/zoo
+ ).map { |path| pn(path) }
+
+ assert_equal pn('/foo'), @ph.longest_common_subpath(paths)
+ end
+
+ test '#longest_common_subpath returns the root directory as an edge case' do
+ paths = %w(
+ /foo/bar
+ /foo/baz
+ /foo/bar/baz/woo/zoo
+ /wadus
+ ).map { |path| pn(path) }
+
+ assert_equal pn('/'), @ph.longest_common_subpath(paths)
+ end
+
+ test '#longest_common_subpath returns nil for an empty collection' do
+ assert_nil @ph.longest_common_subpath([])
+ end
+
+ test '#existing_parent returns the most specific existing ascendant' do
+ wd = Pathname.getwd
+
+ assert_equal wd, @ph.existing_parent(wd)
+ assert_equal wd, @ph.existing_parent(wd.join('non-existing/directory'))
+ assert_equal pn('/'), @ph.existing_parent(pn('/non-existing/directory'))
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (empty)' do
+ assert_equal [], @ph.filter_out_descendants([])
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (one)' do
+ assert_equal ['/foo'], @ph.filter_out_descendants(['/foo'])
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (several)' do
+ paths = %w(
+ /Rails.root/app/controllers
+ /Rails.root/app/models
+ /Rails.root/app/helpers
+ ).map { |path| pn(path) }
+
+ assert_equal paths, @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants filters out descendants preserving order' do
+ paths = %w(
+ /Rails.root/app/controllers
+ /Rails.root/app/controllers/concerns
+ /Rails.root/app/models
+ /Rails.root/app/models/concerns
+ /Rails.root/app/helpers
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0, 2, 4), @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants works on path units' do
+ paths = %w(
+ /foo/bar
+ /foo/barrrr
+ ).map { |path| pn(path) }
+
+ assert_equal paths, @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants deals correctly with the root directory' do
+ paths = %w(
+ /
+ /foo
+ /foo/bar
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0), @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants preserves duplicates' do
+ paths = %w(
+ /foo
+ /foo/bar
+ /foo
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0, 2), @ph.filter_out_descendants(paths)
+ end
+end
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
new file mode 100644
index 0000000000..100cbc9756
--- /dev/null
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -0,0 +1,241 @@
+require 'fileutils'
+
+module FileUpdateCheckerSharedTests
+ extend ActiveSupport::Testing::Declarative
+ include FileUtils
+
+ def tmpdir
+ @tmpdir ||= Dir.mktmpdir(nil, __dir__)
+ end
+
+ def tmpfile(name)
+ "#{tmpdir}/#{name}"
+ end
+
+ def tmpfiles
+ @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
+ end
+
+ def teardown
+ FileUtils.rm_rf(@tmpdir) if defined? @tmpdir
+ end
+
+ test 'should not execute the block if no paths are given' do
+ i = 0
+
+ checker = new_checker { i += 1 }
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'should not execute the block if no files change' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'should execute the block once when files are created' do
+ i = 0
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block once when files are modified' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block once when files are deleted' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ rm_f(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+
+ test 'updated should become true when watched files are created' do
+ i = 0
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ end
+
+
+ test 'updated should become true when watched files are modified' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ end
+
+ test 'updated should become true when watched files are deleted' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ rm_f(tmpfiles)
+
+ assert checker.updated?
+ end
+
+ test 'should be robust to handle files with wrong modified time' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ now = Time.now
+ time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
+ File.utime(time, time, tmpfiles[0])
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles[1..-1])
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should cache updated result until execute' do
+ i = 0
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ checker.execute
+ assert !checker.updated?
+ end
+
+ test 'should execute the block if files change in a watched directory one extension' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :rb) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block if files change in a watched directory several extensions' do
+ i = 0
+
+ checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+
+ touch(tmpfile('foo.txt'))
+
+ assert checker.execute_if_updated
+ assert_equal 2, i
+ end
+
+ test 'should not execute the block if the file extension is not watched' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :txt) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'does not assume files exist on instantiation' do
+ i = 0
+
+ non_existing = tmpfile('non_existing.rb')
+ checker = new_checker([non_existing]) { i += 1 }
+
+ touch(non_existing)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'detects files in new subdirectories' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :rb) { i += 1 }
+
+ subdir = tmpfile('subdir')
+ mkdir(subdir)
+ wait
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+
+ touch("#{subdir}/nested.rb")
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'looked up extensions are inherited in subdirectories not listening to them' do
+ i = 0
+
+ subdir = tmpfile('subdir')
+ mkdir(subdir)
+
+ checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 }
+
+ touch(tmpfile('new.txt'))
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+
+ # subdir does not look for Ruby files, but its parent tmpdir does.
+ touch("#{subdir}/nested.rb")
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+
+ touch("#{subdir}/nested.txt")
+
+ assert checker.execute_if_updated
+ assert_equal 2, i
+ end
+end
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index bd1df0f858..752f7836cd 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,112 +1,19 @@
require 'abstract_unit'
-require 'fileutils'
-require 'thread'
+require 'file_update_checker_shared_tests'
-MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+class FileUpdateCheckerTest < ActiveSupport::TestCase
+ include FileUpdateCheckerSharedTests
-class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
- FILES = %w(1.txt 2.txt 3.txt)
-
- def setup
- FileUtils.mkdir_p("tmp_watcher")
- FileUtils.touch(FILES)
- end
-
- def teardown
- FileUtils.rm_rf("tmp_watcher")
- FileUtils.rm_rf(FILES)
- end
-
- def test_should_not_execute_the_block_if_no_paths_are_given
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
- checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_invoke_the_block_if_no_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- 5.times { assert !checker.execute_if_updated }
- assert_equal 0, i
- end
-
- def test_should_invoke_the_block_if_a_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- sleep(1)
- FileUtils.touch(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_enough_to_handle_deleted_files
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- FileUtils.rm(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_to_handle_files_with_wrong_modified_time
- i = 0
- now = Time.now
- time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
- File.utime time, time, FILES[2]
-
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
-
- sleep(1)
- FileUtils.touch(FILES[0..1])
-
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_cache_updated_result_until_execute
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- assert !checker.updated?
-
- sleep(1)
- FileUtils.touch(FILES)
-
- assert checker.updated?
- checker.execute
- assert !checker.updated?
- end
-
- def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert checker.execute_if_updated
- assert_equal 1, i
+ def new_checker(files = [], dirs = {}, &block)
+ ActiveSupport::FileUpdateChecker.new(files, dirs, &block)
end
- def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert !checker.execute_if_updated
- assert_equal 0, i
+ def wait
+ # noop
end
- def test_should_not_block_if_a_strange_filename_used
- FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
- FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
-
- test = Thread.new do
- ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
- Thread.exit
- end
- test.priority = -1
- test.join(5)
-
- assert !test.alive?
+ def touch(files)
+ sleep 1 # let's wait a bit to ensure there's a new mtime
+ super
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index a0764f6d6b..06cd41c86d 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -269,14 +269,32 @@ class InflectorTest < ActiveSupport::TestCase
def test_parameterize_with_custom_separator
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
- assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_'))
+ assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: '_'))
+ end
+ end
+
+ def test_parameterize_with_custom_separator_deprecated
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
+ StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
+ assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_'))
+ end
end
end
def test_parameterize_with_multi_character_separator
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
- assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__'))
+ assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, separator: '__sep__'))
+ end
+ end
+
+ def test_parameterize_with_multi_character_separator_deprecated
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
+ StringToParameterized.each do |some_string, parameterized_string|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do
+ assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__'))
+ end
end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index e6898658b5..14fe97a986 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -174,6 +174,17 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test-with-malformed-utf8"
}
+ StringToParameterizedPreserveCase = {
+ "Donald E. Knuth" => "Donald-E-Knuth",
+ "Random text with *(bad)* characters" => "Random-text-with-bad-characters",
+ "Allow_Under_Scores" => "Allow_Under_Scores",
+ "Trailing bad characters!@#" => "Trailing-bad-characters",
+ "!@#Leading bad characters" => "Leading-bad-characters",
+ "Squeeze separators" => "Squeeze-separators",
+ "Test with + sign" => "Test-with-sign",
+ "Test with malformed utf8 \xA9" => "Test-with-malformed-utf8"
+ }
+
StringToParameterizeWithNoSeparator = {
"Donald E. Knuth" => "donaldeknuth",
"With-some-dashes" => "with-some-dashes",
@@ -185,6 +196,17 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "testwithmalformedutf8"
}
+ StringToParameterizePreserveCaseWithNoSeparator = {
+ "Donald E. Knuth" => "DonaldEKnuth",
+ "With-some-dashes" => "With-some-dashes",
+ "Random text with *(bad)* characters" => "Randomtextwithbadcharacters",
+ "Trailing bad characters!@#" => "Trailingbadcharacters",
+ "!@#Leading bad characters" => "Leadingbadcharacters",
+ "Squeeze separators" => "Squeezeseparators",
+ "Test with + sign" => "Testwithsign",
+ "Test with malformed utf8 \xA9" => "Testwithmalformedutf8"
+ }
+
StringToParameterizeWithUnderscore = {
"Donald E. Knuth" => "donald_e_knuth",
"Random text with *(bad)* characters" => "random_text_with_bad_characters",
@@ -197,6 +219,18 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test_with_malformed_utf8"
}
+ StringToParameterizePreserceCaseWithUnderscore = {
+ "Donald E. Knuth" => "Donald_E_Knuth",
+ "Random text with *(bad)* characters" => "Random_text_with_bad_characters",
+ "With-some-dashes" => "With-some-dashes",
+ "Allow_Under_Scores" => "Allow_Under_Scores",
+ "Trailing bad characters!@#" => "Trailing_bad_characters",
+ "!@#Leading bad characters" => "Leading_bad_characters",
+ "Squeeze separators" => "Squeeze_separators",
+ "Test with + sign" => "Test_with_sign",
+ "Test with malformed utf8 \xA9" => "Test_with_malformed_utf8"
+ }
+
StringToParameterizedAndNormalized = {
"Malmö" => "malmo",
"Garçons" => "garcons",
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index ad41db608b..465a657308 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'concurrent/atomics'
+require 'concurrent/atomic/count_down_latch'
require 'active_support/concurrency/share_lock'
class ShareLockTest < ActiveSupport::TestCase
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index f6871c186e..f16d509f77 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -154,7 +154,7 @@ Railties
rails g scaffold Post title:string:index author:uniq price:decimal{7,2}
```
- will create indexes for `title` and `author` with the latter being an unique index. Some types such as decimal accept custom options. In the example, `price` will be a decimal column with precision and scale set to 7 and 2 respectively.
+ will create indexes for `title` and `author` with the latter being a unique index. Some types such as decimal accept custom options. In the example, `price` will be a decimal column with precision and scale set to 7 and 2 respectively.
* Turn gem has been removed from default Gemfile.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index a114686f0f..e36c0f899f 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -83,7 +83,7 @@ Note that you can define `perform` with as many arguments as you want.
Enqueue a job like so:
```ruby
-# Enqueue a job to be performed as soon the queuing system is
+# Enqueue a job to be performed as soon as the queuing system is
# free.
GuestsCleanupJob.perform_later guest
```
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index fe2501bd87..8f8256c983 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -8,10 +8,10 @@ classes. Active Model allows for Action Pack helpers to interact with
plain Ruby objects. Active Model also helps build custom ORMs for use
outside of the Rails framework.
-After reading this guide, you will know:
+After reading this guide, you will know:
* How an Active Record model behaves.
-* How Callbacks and validations work.
+* How Callbacks and validations work.
* How serializers work.
* The Rails internationalization (i18n) framework.
@@ -428,7 +428,7 @@ the Active Model API.
class PersonTest < ActiveSupport::TestCase
include ActiveModel::Lint::Tests
- def setup
+ setup do
@model = Person.new
end
end
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 67881e6087..5aa5dc4f60 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -454,8 +454,6 @@ number of digits after the decimal point.
are using a dynamic value (such as a date), the default will only be calculated
the first time (i.e. on the date the migration is applied).
* `index` Adds an index for the column.
-* `required` Adds `required: true` for `belongs_to` associations and
-`null: false` to the column in the migration.
Some adapters may support additional options; see the adapter specific API docs
for further information.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 8ea0f383c0..ec31fa9d67 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -69,6 +69,7 @@ The methods are:
* `having`
* `includes`
* `joins`
+* `left_outer_joins`
* `limit`
* `lock`
* `none`
@@ -80,7 +81,7 @@ The methods are:
* `reorder`
* `reverse_order`
* `select`
-* `uniq`
+* `distinct`
* `where`
All of the above methods return an instance of `ActiveRecord::Relation`.
@@ -184,6 +185,8 @@ SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
The `first` method returns `nil` if no matching record is found and no exception will be raised.
+If your [default scope](active_record_querying.html#applying-a-default-scope) contains an order method, `first` will return the first record according to this ordering.
+
You can pass in a numerical argument to the `first` method to return up to that number of results. For example
```ruby
@@ -220,6 +223,8 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
The `last` method returns `nil` if no matching record is found and no exception will be raised.
+If your [default scope](active_record_querying.html#applying-a-default-scope) contains an order method, `last` will return the last record according to this ordering.
+
You can pass in a numerical argument to the `last` method to return up to that number of results. For example
```ruby
@@ -935,25 +940,30 @@ end
Joining Tables
--------------
-Active Record provides a finder method called `joins` for specifying `JOIN` clauses on the resulting SQL. There are multiple ways to use the `joins` method.
+Active Record provides two finder methods for specifying `JOIN` clauses on the
+resulting SQL: `joins` and `left_outer_joins`.
+While `joins` should be used for `INNER JOIN` or custom queries,
+`left_outer_joins` is used for queries using `LEFT OUTER JOIN`.
+
+### `joins`
+
+There are multiple ways to use the `joins` method.
-### Using a String SQL Fragment
+#### Using a String SQL Fragment
You can just supply the raw SQL specifying the `JOIN` clause to `joins`:
```ruby
-Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
+Author.joins("INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'")
```
This will result in the following SQL:
```sql
-SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
+SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'
```
-### Using Array/Hash of Named Associations
-
-WARNING: This method only works with `INNER JOIN`.
+#### Using Array/Hash of Named Associations
Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method.
@@ -986,7 +996,7 @@ end
Now all of the following will produce the expected join queries using `INNER JOIN`:
-#### Joining a Single Association
+##### Joining a Single Association
```ruby
Category.joins(:articles)
@@ -999,7 +1009,7 @@ SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id
```
-Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`.
+Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).distinct`.
#### Joining Multiple Associations
@@ -1017,7 +1027,7 @@ SELECT articles.* FROM articles
Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times.
-#### Joining Nested Associations (Single Level)
+##### Joining Nested Associations (Single Level)
```ruby
Article.joins(comments: :guest)
@@ -1033,7 +1043,7 @@ SELECT articles.* FROM articles
Or, in English: "return all articles that have a comment made by a guest."
-#### Joining Nested Associations (Multiple Level)
+##### Joining Nested Associations (Multiple Level)
```ruby
Category.joins(articles: [{ comments: :guest }, :tags])
@@ -1049,7 +1059,7 @@ SELECT categories.* FROM categories
INNER JOIN tags ON tags.article_id = articles.id
```
-### Specifying Conditions on the Joined Tables
+#### Specifying Conditions on the Joined Tables
You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provide a special syntax for specifying conditions for the joined tables:
@@ -1067,6 +1077,26 @@ Client.joins(:orders).where(orders: { created_at: time_range })
This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression.
+### `left_outer_joins`
+
+If you want to select a set of records whether or not they have associated
+records you can use the `left_outer_joins` method.
+
+```ruby
+Author.left_outer_joins(:posts).uniq.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id')
+```
+
+Which produces:
+
+```sql
+SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors"
+LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id
+```
+
+Which means: "return all authors with their count of posts, whether or not they
+have any posts at all"
+
+
Eager Loading Associations
--------------------------
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index dd4d9f55fa..fe42cec158 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -457,21 +457,6 @@ class Person < ActiveRecord::Base
end
```
-This helper counts characters by default, but you can split the value in a
-different way using the `:tokenizer` option:
-
-```ruby
-class Essay < ActiveRecord::Base
- validates :content, length: {
- minimum: 300,
- maximum: 400,
- tokenizer: lambda { |str| str.split(/\s+/) },
- too_short: "must have at least %{count} words",
- too_long: "must have at most %{count} words"
- }
-end
-```
-
Note that the default error messages are plural (e.g., "is too short (minimum
is %{count} characters)"). For this reason, when `:minimum` is 1 you should
provide a personalized message or use `presence: true` instead. When
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 556b5ede3c..181dca4b71 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -248,6 +248,13 @@ end
@person.try { |p| "#{p.first_name} #{p.last_name}" }
```
+Note that `try` will swallow no-method errors, returning nil instead. If you want to protect against typos, use `try!` instead:
+
+```ruby
+@number.try(:nest) # => nil
+@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Fixnum
+```
+
NOTE: Defined in `active_support/core_ext/object/try.rb`.
### `class_eval(*args, &block)`
@@ -1703,6 +1710,20 @@ The method `parameterize` normalizes its receiver in a way that can be used in p
"Kurt Gödel".parameterize # => "kurt-godel"
```
+To preserve the case of the string, set the `preserve_case` argument to true. By default, `preserve_case` is set to false.
+
+```ruby
+"John Smith".parameterize(preserve_case: true) # => "John-Smith"
+"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"
+```
+
+To use a custom separator, override the `separator` argument.
+
+```ruby
+"John Smith".parameterize(separator: "_") # => "john\_smith"
+"Kurt Gödel".parameterize(separator: "_") # => "kurt\_godel"
+```
+
In fact, the result string is wrapped in an instance of `ActiveSupport::Multibyte::Chars`.
NOTE: Defined in `active_support/core_ext/string/inflections.rb`.
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index e5a560edd0..0fd0112c9f 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -232,6 +232,7 @@ Active Record
| `:sql` | SQL statement |
| `:name` | Name of the operation |
| `:connection_id` | `self.object_id` |
+| `:binds` | Bind parameters |
INFO. The adapters will add their own data as well.
@@ -457,7 +458,7 @@ The block receives the following arguments:
* The name of the event
* Time when it started
* Time when it finished
-* An unique ID for this event
+* A unique ID for this event
* The payload (described in previous sections)
```ruby
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index feaaff166a..fb3127555e 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -221,7 +221,7 @@ For instance, using the `stale?` method:
```ruby
def show
- @post = Post.find(params[:id])
+ @post = Post.find(params[:id])
if stale?(last_modified: @post.updated_at)
render json: @post
@@ -240,7 +240,7 @@ cross-client caching in the call to `stale?`:
```ruby
def show
- @post = Post.find(params[:id])
+ @post = Post.find(params[:id])
if stale?(last_modified: @post.updated_at, public: true)
render json: @post
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 41881abb62..0f2283318a 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -169,7 +169,7 @@ directory. Files in this directory are served by the Sprockets middleware.
Assets can still be placed in the `public` hierarchy. Any assets under `public`
will be served as static files by the application or web server when
-`config.serve_static_files` is set to true. You should use `app/assets` for
+`config.public_file_server.enabled` is set to true. You should use `app/assets` for
files that must undergo some pre-processing before they are served.
In production, Rails precompiles these files to `public/assets` by default. The
@@ -1021,7 +1021,7 @@ header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C
specification that describes how a request can be cached. When no CDN is used, a
browser will use this information to cache contents. This is very helpful for
assets that are not modified so that a browser does not need to re-download a
-website's CSS or javascript on every request. Generally we want our Rails server
+website's CSS or JavaScript on every request. Generally we want our Rails server
to tell our CDN (and browser) that the asset is "public", that means any cache
can store the request. Also we commonly want to set `max-age` which is how long
the cache will store the object before invalidating the cache. The `max-age`
@@ -1029,7 +1029,9 @@ value is set to seconds with a maximum possible value of `31536000` which is one
year. You can do this in your rails application by setting
```
-config.static_cache_control = "public, max-age=31536000"
+config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=31536000'
+}
```
Now when your application serves an asset in production, the CDN will store the
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 999c533fb3..c272daac28 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -16,7 +16,7 @@ After reading this guide, you will know:
Why Associations?
-----------------
-Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this:
+In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this:
```ruby
class Customer < ActiveRecord::Base
@@ -71,7 +71,7 @@ To learn more about the different types of associations, read the next section o
The Types of Associations
-------------------------
-In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations:
+Rails supports six types of associations:
* `belongs_to`
* `has_one`
@@ -80,6 +80,8 @@ In Rails, an _association_ is a connection between two Active Record models. Ass
* `has_one :through`
* `has_and_belongs_to_many`
+Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain [Primary Key](https://en.wikipedia.org/wiki/Unique_key)-[Foreign Key](https://en.wikipedia.org/wiki/Foreign_key) information between instances of the two models, and you also get a number of utility methods added to your model.
+
In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate.
### The `belongs_to` Association
@@ -238,13 +240,15 @@ class CreateAppointments < ActiveRecord::Migration
end
```
-The collection of join models can be managed via the API. For example, if you assign
+The collection of join models can be managed via the [`has_many` association methods](#has-many-association-reference).
+For example, if you assign:
```ruby
physician.patients = patients
```
-new join models are created for newly associated objects, and if some are gone their rows are deleted.
+Then new join models are automatically created for the newly associated objects.
+If some that existed previously are now missing, then their join rows are automatically deleted.
WARNING: Automatic deletion of join models is direct, no destroy callbacks are triggered.
@@ -2163,7 +2167,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
* `order`
* `readonly`
* `select`
-* `uniq`
+* `distinct`
##### `where`
@@ -2239,9 +2243,9 @@ If you use the `readonly` method, then the associated objects will be read-only
The `select` method lets you override the SQL `SELECT` clause that is used to retrieve data about the associated objects. By default, Rails retrieves all columns.
-##### `uniq`
+##### `distinct`
-Use the `uniq` method to remove duplicates from the collection.
+Use the `distinct` method to remove duplicates from the collection.
#### When are Objects Saved?
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 2b6d7e4044..5126d87bee 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -790,7 +790,7 @@ Constant Reloading
When `config.cache_classes` is false Rails is able to reload autoloaded
constants.
-For example, in you're in a console session and edit some file behind the
+For example, if you're in a console session and edit some file behind the
scenes, the code can be reloaded with the `reload!` command:
```
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index e2206667e8..729d1cc661 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -122,7 +122,7 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`.
-* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
+* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified:
@@ -199,7 +199,7 @@ The full set of methods that can be used in this block are as follows:
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`.
-* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`. Set `config.static_index` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.static_index` to `"main"`.
+* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`.
* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
* `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
* `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request.
@@ -313,6 +313,10 @@ All these configuration options are delegated to the `I18n` library.
by a query exceeds the threshold, a warning is logged. This can be used to
identify queries which might be causing memory bloat.
+* `config.active_record.index_nested_attribute_errors` allows errors for nested
+ has_many relationships to be displayed with an index as well as the error.
+ Defaults to false.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
@@ -1145,3 +1149,25 @@ Disallow: /
To block just specific pages, it's necessary to use a more complex syntax. Learn
it on the [official documentation](http://www.robotstxt.org/robotstxt.html).
+
+Evented File System Monitor
+---------------------------
+
+If the [listen gem](https://github.com/guard/listen) is loaded Rails uses an
+evented file system monitor to detect changes when `config.cache_classes` is
+false:
+
+```ruby
+group :development do
+ gem 'listen', '~> 3.0.4'
+end
+```
+
+Otherwise, in every request Rails walks the application tree to check if
+anything has changed.
+
+On Linux and Mac OS X no additional gems are needed, but some are required
+[for *BSD](https://github.com/guard/listen#on-bsd) and
+[for Windows](https://github.com/guard/listen#on-windows).
+
+Note that [some setups are unsupported](https://github.com/guard/listen#issues--limitations).
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 6d689804a8..53c3cbf80b 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -128,11 +128,11 @@ Contributing to the Rails Documentation
Ruby on Rails has two main sets of documentation: the guides, which help you
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. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides).
+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](http://github.com/rails/rails) or
ask the [Rails core team](http://rubyonrails.org/core) for commit access on
-[docrails](http://github.com/rails/docrails) if you contribute regularly.
+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](http://github.com/rails/rails) instead.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index f961b799f1..359796b1ff 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -239,6 +239,27 @@ NOTE: The `ApplicationController` class inside an engine is named just like a
Rails application in order to make it easier for you to convert your
applications into engines.
+NOTE: Because of the way that Ruby does constant lookup you may run into a situation
+where your engine controller is inheriting from the main application controller and
+not your engine's application controller. Ruby is able to resolve the `ApplicationController` constant, and therefore the autoloading mechanism is not triggered. See the section [When Constants Aren't Missed](autoloading_and_reloading_constants.html#when-constants-aren-t-missed) of the [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html) guide for further details. The best way to prevent this from
+happening is to use `require_dependency` to ensure that the engine's application
+controller is loaded. For example:
+
+``` ruby
+# app/controllers/blorgh/articles_controller.rb:
+require_dependency "blorgh/application_controller"
+
+module Blorgh
+ class ArticlesController < ApplicationController
+ ...
+ end
+end
+```
+
+WARNING: Don't use `require` because it will break the automatic reloading of classes
+in the development environment - using `require_dependency` ensures that classes are
+loaded and unloaded in the correct manner.
+
Lastly, the `app/views` directory contains a `layouts` folder, which contains a
file at `blorgh/application.html.erb`. This file allows you to specify a layout
for the engine. If this engine is to be used as a stand-alone engine, then you
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 0a6e2e5dba..93bb51557a 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -657,7 +657,7 @@ NOTE: If the user has not selected a file the corresponding parameter will be an
### Dealing with Ajax
-Unlike other forms making an asynchronous file upload form is not as simple as providing `form_for` with `remote: true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.
+Unlike other forms, making an asynchronous file upload form is not as simple as providing `form_for` with `remote: true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.
Customizing Form Builders
-------------------------
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 87d2fafaf3..8381636196 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -805,6 +805,8 @@ en:
Then `User.human_attribute_name("gender.female")` will return "Female".
+NOTE: If you are using a class which includes `ActiveModel` and does not inherit from `ActiveRecord::Base`, replace `activerecord` with `activemodel` in the above key paths.
+
#### Error Message Scopes
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 43083ebb86..ebe1cb206a 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -86,10 +86,9 @@ The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot
`config/boot.rb` contains:
```ruby
-# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+require 'bundler/setup' # Set up gems listed in the Gemfile.
```
In a standard Rails application, there's a `Gemfile` which declares all
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index b94c26a1ae..922bbb4f73 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -294,7 +294,7 @@ Getting closer... Now we will implement the code of the `acts_as_yaffle` method
module Yaffle
module ActsAsYaffle
- extend ActiveSupport::Concern
+ extend ActiveSupport::Concern
included do
end
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 87f869aff3..273fbc08e2 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -213,7 +213,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::Static`**
-* Used to serve static files. Disabled if `config.serve_static_files` is `false`.
+* Used to serve static files from the public directory. Disabled if `config.public_file_server.enabled` is `false`.
**`Rack::Lock`**
diff --git a/guides/source/security.md b/guides/source/security.md
index fb9ee7b412..df8c24864e 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1046,7 +1046,7 @@ If you want an exception to be raised when some key is blank, use the bang
version:
```ruby
-Rails.application.secrets.some_api_key! # => raises KeyError
+Rails.application.secrets.some_api_key! # => raises KeyError: key not found: :some_api_key
```
Additional Resources
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 435de30acc..3bfbf4f7ff 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -54,10 +54,12 @@ NOTE: Your tests are run under `RAILS_ENV=test`.
### Rails meets Minitest
-If you remember when you used the `rails generate scaffold` command from the [Getting Started with Rails](getting_started.html) guide. We created our first resource among other things it created test stubs in the `test` directory:
+If you remember when you used the `rails generate model` command from the
+[Getting Started with Rails](getting_started.html) guide. We created our first
+model among other things it created test stubs in the `test` directory:
```bash
-$ bin/rails generate scaffold article title:string body:text
+$ bin/rails generate model article title:string body:text
...
create app/models/article.rb
create test/models/article_test.rb
@@ -155,7 +157,7 @@ Failed assertion, no message given.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
```
-In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:
+In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message that mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:
```ruby
test "should not save article without title" do
@@ -319,7 +321,7 @@ Rails adds some custom assertions of its own to the `minitest` framework:
| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
-| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
+| `assert_redirected_to(options = {}, message=nil)` | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
You'll see the usage of some of these assertions in the next chapter.
@@ -523,7 +525,7 @@ Model tests don't have their own superclass like `ActionMailer::TestCase` instea
Integration Testing
-------------------
-Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
+Integration tests are used to test how various parts of your application interact. They are generally used to test important workflows within your application.
For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
@@ -649,14 +651,31 @@ You should test for things such as:
* was the correct object stored in the response template?
* was the appropriate message displayed to the user in the view?
-Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
+The easiest way to see functional tests in action is to generate a controller
+scaffold:
-The following command will generate a controller test case with a filled up
-test for each of the seven default actions.
+```bash
+$ bin/rails generate scaffold_controller article title:string body:test
+...
+create app/controllers/articles_controller.rb
+...
+invoke test_unit
+create test/controllers/articles_controller_test.rb
+...
+```
+
+This will generate the controller code and tests for an `Article` resource.
+You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
+
+If you already have a controller and just want to generate the test scaffold code for
+each of the seven default actions, you can use the following command:
```bash
$ bin/rails generate test_unit:scaffold article
+...
+invoke test_unit
create test/controllers/articles_controller_test.rb
+...
```
Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
@@ -908,12 +927,12 @@ require 'test_helper'
class ArticlesControllerTest < ActionController::TestCase
# called before every single test
- def setup
+ setup do
@article = articles(:one)
end
# called after every single test
- def teardown
+ teardown do
# when controller is using cache it may be a good idea to reset it afterwards
Rails.cache.clear
end
@@ -1232,3 +1251,24 @@ class ProductTest < ActiveJob::TestCase
end
end
```
+
+Testing Time-Dependent Code
+---------------------------
+
+Rails provides inbuilt helper methods that enable you to assert that your time-sensitve code works as expected.
+
+Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper:
+
+```ruby
+# Lets say that a user is eligible for gifting a month after they register.
+user = User.create(name: 'Gaurish', activation_date: Date.new(2004, 10, 24))
+assert_not user.applicable_for_gifting?
+travel_to Date.new(2004, 11, 24) do
+ assert_equal Date.new(2004, 10, 24), user.activation_date # inside the travel_to block `Date.current` is mocked
+ assert user.applicable_for_gifting?
+end
+assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.
+```
+
+Please see [`ActiveSupport::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html)
+for in-depth information about the available time helpers.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 490bda3571..fa6a01671b 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1090,7 +1090,7 @@ config.active_record.auto_explain_threshold_in_seconds = 0.5
### config/environments/test.rb
-The `mass_assignment_sanitizer` configuration setting should also be be added to `config/environments/test.rb`:
+The `mass_assignment_sanitizer` configuration setting should also be added to `config/environments/test.rb`:
```ruby
# Raise exception on mass assignment protection for Active Record models
@@ -1191,8 +1191,10 @@ You can help test performance with these additions to your test environment:
```ruby
# Configure static asset server for tests with Cache-Control for performance
-config.serve_static_files = true
-config.static_cache_control = 'public, max-age=3600'
+config.public_file_server.enabled = true
+config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=3600'
+}
```
### config/initializers/wrap_parameters.rb
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 9337dd6407..aa12708669 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,66 @@
+* Make `static_index` part of the `config.public_file_server` config and
+ call it `public_file_server.index_name`.
+
+ *Yuki Nishijima*
+
+* Generated `Gemfile`s for new applications include a new dependency on
+ [listen](https://github.com/guard/listen) commented out.
+
+ *Puneet Agarwal* and *Xavier Noria*
+
+* Deprecate `serve_static_files` in favor of `public_file_server.enabled`.
+
+ Unifies the static asset options under `public_file_server`.
+
+ To upgrade, replace occurrences of:
+
+ ```
+ config.serve_static_files = # false or true
+ ```
+
+ in your environment files, with:
+
+ ```
+ config.public_file_server.enabled = # false or true
+ ```
+
+ *Kasper Timm Hansen*
+
+* Deprecate `config.static_cache_control` in favor of
+ `config.public_file_server.headers`.
+
+ To upgrade, replace occurrences of:
+
+ ```
+ config.static_cache_control = 'public, max-age=60'
+ ```
+
+ in your environment files, with:
+
+ ```
+ config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=60'
+ }
+ ```
+
+ `config.public_file_server.headers` can set arbitrary headers, sent along when
+ a response is delivered.
+
+ *Yuki Nishijima*
+
+* Route generator should be idempotent
+ running generators several times no longer require you to cleanup routes.rb
+
+ *Thiago Pinto*
+
+* Allow passing an environment to `config_for`.
+
+ *Simon Eskildsen*
+
+* Allow rake:stats to account for rake tasks in lib/tasks
+
+ *Kevin Deisz*
+
* Added javascript to update the URL on mailer previews with the currently
selected email format. Reloading the page now keeps you on your selected
format rather than going back to the default html version.
diff --git a/railties/Rakefile b/railties/Rakefile
index cf130a5f14..73d881b318 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -5,20 +5,33 @@ task :default => :test
desc "Run all unit tests"
task :test => 'test:isolated'
+dash_i = [
+ 'test',
+ 'lib',
+ "#{File.dirname(__FILE__)}/../activesupport/lib",
+ "#{File.dirname(__FILE__)}/../actionpack/lib",
+ "#{File.dirname(__FILE__)}/../activemodel/lib"
+]
+
+dash_i.reverse_each do |x|
+ $:.unshift x unless $:.include? x
+end
+$-w = true
+
+require 'bundler/setup' unless defined?(Bundler)
+require 'active_support'
+
namespace :test do
task :isolated do
dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",")
test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" }
Dir[*test_files].each do |file|
next true if file.include?("fixtures")
- dash_i = [
- 'test',
- 'lib',
- "#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib",
- "#{File.dirname(__FILE__)}/../activemodel/lib"
- ]
- ruby "-w", "-I#{dash_i.join ':'}", file
+ puts "#{FileUtils::RUBY} -w -I#{dash_i.join ':'} #{file}"
+
+ # We could run these in parallel, but pretty much all of the
+ # railties tests already run in parallel, so ¯\_(⊙︿⊙)_/¯
+ Process.waitpid fork { ARGV.clear; load file }
end
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 7916e24af1..77efe3248d 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -167,6 +167,9 @@ module Rails
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
@caching_key_generator ||=
if secrets.secret_key_base
+ unless secrets.secret_key_base.kind_of?(String)
+ raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`"
+ end
key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
@@ -215,12 +218,12 @@ module Rails
# Rails.application.configure do
# config.middleware.use ExceptionNotifier, config_for(:exception_notification)
# end
- def config_for(name)
+ def config_for(name, env: Rails.env)
yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
if yaml.exist?
require "erb"
- (YAML.load(ERB.new(yaml.read).result) || {})[Rails.env] || {}
+ (YAML.load(ERB.new(yaml.read).result) || {})[env] || {}
else
raise "Could not load configuration. No such file - #{yaml}"
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index ee9c87b5cf..80733c2d90 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -3,6 +3,9 @@ require 'active_support/file_update_checker'
require 'rails/engine/configuration'
require 'rails/source_annotation_extractor'
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/strip' # for strip_heredoc
+
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
@@ -11,7 +14,7 @@ module Rails
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
- :serve_static_files, :ssl_options, :static_index, :public_file_server,
+ :ssl_options, :public_file_server,
:session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x
@@ -26,9 +29,9 @@ module Rails
@filter_parameters = []
@filter_redirect = []
@helpers_paths = []
- @serve_static_files = true
- @static_index = "index"
@public_file_server = ActiveSupport::OrderedOptions.new
+ @public_file_server.enabled = true
+ @public_file_server.index_name = "index"
@force_ssl = false
@ssl_options = {}
@session_store = :cookie_store
@@ -41,7 +44,7 @@ module Rails
@railties_order = [:all]
@relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
@reload_classes_only_on_change = true
- @file_watcher = ActiveSupport::FileUpdateChecker
+ @file_watcher = file_update_checker
@exceptions_app = nil
@autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@@ -53,13 +56,34 @@ module Rails
end
def static_cache_control=(value)
- ActiveSupport::Deprecation.warn("static_cache_control is deprecated and will be removed in Rails 5.1. " \
- "Please use `config.public_file_server.headers = {'Cache-Control' => #{value}} " \
- "instead.")
+ ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
+ `static_cache_control` is deprecated and will be removed in Rails 5.1.
+ Please use
+ `config.public_file_server.headers = { 'Cache-Control' => '#{value}' }`
+ instead.
+ eow
@static_cache_control = value
end
+ def serve_static_files
+ ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
+ `serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `public_file_server.enabled` instead.
+ eow
+
+ @public_file_server.enabled
+ end
+
+ def serve_static_files=(value)
+ ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
+ `serve_static_files` is deprecated and will be removed in Rails 5.1.
+ Please use `public_file_server.enabled = #{value}` instead.
+ eow
+
+ @public_file_server.enabled = value
+ end
+
def encoding=(value)
@encoding = value
silence_warnings do
@@ -157,6 +181,14 @@ module Rails
end
private
+ def file_update_checker
+ if defined?(Listen) && Listen::Adapter.select() != Listen::Adapter::Polling
+ ActiveSupport::FileEventedUpdateChecker
+ else
+ ActiveSupport::FileUpdateChecker
+ end
+ end
+
class Custom #:nodoc:
def initialize
@configurations = Hash.new
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 387d92db73..5cb5bfb8b7 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -17,11 +17,11 @@ module Rails
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
- if config.serve_static_files
+ if config.public_file_server.enabled
headers = config.public_file_server.headers || {}
headers['Cache-Control'.freeze] = config.static_cache_control if config.static_cache_control
- middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.static_index, headers: headers
+ middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers
end
if rack_cache = load_rack_cache
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index fd352dc9b7..8e9097e1ef 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -33,7 +33,7 @@ class CodeStatistics #:nodoc:
Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
end
- def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/)
+ def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|rake)$/)
stats = CodeStatisticsCalculator.new
Dir.foreach(directory) do |file_name|
diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb
index a142236dbe..fad13e8517 100644
--- a/railties/lib/rails/code_statistics_calculator.rb
+++ b/railties/lib/rails/code_statistics_calculator.rb
@@ -25,6 +25,7 @@ class CodeStatisticsCalculator #:nodoc:
}
PATTERNS[:minitest] = PATTERNS[:rb].merge method: /^\s*(def|test)\s+['"_a-z]/
+ PATTERNS[:rake] = PATTERNS[:rb]
def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0)
@lines = lines
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index b4356f71e0..5bbd2f1aed 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -235,7 +235,7 @@ module Rails
sentinel = /\.routes\.draw do\s*\n/m
in_root do
- inject_into_file 'config/routes.rb', " #{routing_code}\n", { after: sentinel, verbose: false, force: true }
+ inject_into_file 'config/routes.rb', " #{routing_code}\n", { after: sentinel, verbose: false, force: false }
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 0f44f4694e..56c9d3e354 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -162,7 +162,8 @@ module Rails
def database_gemfile_entry
return [] if options[:skip_active_record]
- GemfileEntry.version gem_for_database, nil,
+ gem_name, gem_version = gem_for_database
+ GemfileEntry.version gem_name, gem_version,
"Use #{options[:database]} as the database for Active Record"
end
@@ -202,6 +203,16 @@ module Rails
def self.path(name, path, comment = nil)
new(name, nil, comment, path: path)
end
+
+ def version
+ version = super
+
+ if version.is_a?(Array)
+ version.join("', '")
+ else
+ version
+ end
+ end
end
def rails_gemfile_entry
@@ -230,16 +241,16 @@ module Rails
def gem_for_database
# %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql )
case options[:database]
- when "oracle" then "ruby-oci8"
- when "postgresql" then "pg"
- when "frontbase" then "ruby-frontbase"
- when "mysql" then "mysql2"
- when "sqlserver" then "activerecord-sqlserver-adapter"
- when "jdbcmysql" then "activerecord-jdbcmysql-adapter"
- when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter"
- when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter"
- when "jdbc" then "activerecord-jdbc-adapter"
- else options[:database]
+ when "oracle" then ["ruby-oci8", nil]
+ when "postgresql" then ["pg", ["~> 0.18"]]
+ when "frontbase" then ["ruby-frontbase", nil]
+ when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]]
+ when "sqlserver" then ["activerecord-sqlserver-adapter", nil]
+ when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil]
+ when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil]
+ when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil]
+ when "jdbc" then ["activerecord-jdbc-adapter", nil]
+ else [options[:database], nil]
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 975be07622..4f3f59cc22 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -48,6 +48,10 @@ group :development do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
+
+ # Loading the listen gem enables an evented file system monitor. Check
+ # https://github.com/guard/listen#listen-adapters if on Windows or *BSD.
+ # gem 'listen', '~> 3.0.4'
end
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 75ea52828e..8bb4440f55 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -1,23 +1,24 @@
<!DOCTYPE html>
<html>
-<head>
- <title><%= camelized %></title>
- <%- if options[:skip_javascript] -%>
- <%%= stylesheet_link_tag 'application', media: 'all' %>
- <%- else -%>
- <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
- <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
- <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ <head>
+ <title><%= camelized %></title>
+ <%- if options[:skip_javascript] -%>
+ <%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
- <%%= stylesheet_link_tag 'application', media: 'all' %>
- <%%= javascript_include_tag 'application' %>
+ <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
+ <%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
+ <%%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ <%- else -%>
+ <%%= stylesheet_link_tag 'application', media: 'all' %>
+ <%%= javascript_include_tag 'application' %>
+ <%- end -%>
<%- end -%>
- <%- end -%>
- <%%= csrf_meta_tags %>
-</head>
-<body>
+ <%%= csrf_meta_tags %>
+ </head>
-<%%= yield %>
+ <body>
-</body>
+ <%%= yield %>
+
+ </body>
</html>
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 e29f0bacaa..4dd20a9d2e 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
@@ -15,8 +15,10 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
- config.static_cache_control = "public, max-age=172800"
config.cache_store = :memory_store
+ config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=172800'
+ }
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 0297ab75f6..a5302550fa 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -16,7 +16,7 @@ Rails.application.configure do
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
- config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
<%- unless options.skip_sprockets? -%>
# Compress JavaScripts and CSS.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 5165100c22..8133917591 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -12,8 +12,8 @@ Rails.application.configure do
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
- # Configure static file server for tests with Cache-Control for performance.
- config.serve_static_files = true
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=3600'
}
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
index 9fca213a04..3b1c1b5ed1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
@@ -1,5 +1,7 @@
-# Avoid CORS issues when API is called from the frontend app
-# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests
+# Be sure to restart your server when you modify this file.
+
+# Avoid CORS issues when API is called from the frontend app.
+# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index f8ece4fe73..ff242adbab 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -22,6 +22,6 @@ Gem::Specification.new do |s|
<%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>"
<% unless options[:skip_active_record] -%>
- s.add_development_dependency "<%= gem_for_database %>"
+ s.add_development_dependency "<%= gem_for_database[0] %>"
<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index 2c91c6a0ea..f085a2577a 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -11,7 +11,7 @@ gemspec
<% if options[:skip_gemspec] -%>
group :development do
- gem '<%= gem_for_database %>'
+ gem '<%= gem_for_database[0] %>'
end
<% else -%>
# Declare any dependencies that are still in development here instead of in
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore
index d524fcbc4e..54c78d7927 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore
@@ -6,5 +6,4 @@ pkg/
<%= dummy_path %>/db/*.sqlite3-journal
<%= dummy_path %>/log/*.log
<%= dummy_path %>/tmp/
-<%= dummy_path %>/.sass-cache
<% end -%>
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
index bc3c9b3f6b..17a86f376b 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
@@ -1,5 +1,5 @@
<% if namespaced? -%>
-require_dependency "<%= namespaced_file_path %>/application_controller"
+require_dependency "<%= namespaced_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 735c36eb3a..a919d36939 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -9,6 +9,7 @@ STATS_DIRECTORIES = [
%w(Mailers app/mailers),
%w(Javascripts app/assets/javascripts),
%w(Libraries lib/),
+ %w(Tasks lib/tasks),
%w(APIs app/apis),
%w(Controller\ tests test/controllers),
%w(Helper\ tests test/helpers),
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index e1fe92a11b..de4b002e55 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -57,8 +57,14 @@ module Rails
end
def format_rerun_snippet(result)
- location, line = result.method(result.name).source_location
- "#{self.executable} #{relative_path_for(location)}:#{line}"
+ # Try to extract path to assertion from backtrace.
+ if result.location =~ /\[(.*)\]\z/
+ assertion_path = $1
+ else
+ assertion_path = result.method(result.name).source_location.join(':')
+ end
+
+ "#{self.executable} #{relative_path_for(assertion_path)}"
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index dca5cf2e5b..18882e1855 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -240,7 +240,7 @@ module ApplicationTests
test "assets do not require any assets group gem when manifest file is present" do
app_file "app/assets/javascripts/application.js", "alert();"
- add_to_env_config "production", "config.serve_static_files = true"
+ add_to_env_config "production", "config.public_file_server.enabled = true"
precompile! RAILS_ENV: 'production'
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index d96d8ded6b..5f3d1879eb 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -308,37 +308,47 @@ module ApplicationTests
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
- test "In production mode, config.serve_static_files is off by default" do
+ test "In production mode, config.public_file_server.enabled is off by default" do
restore_default_config
with_rails_env "production" do
app 'production'
- assert_not app.config.serve_static_files
+ assert_not app.config.public_file_server.enabled
end
end
- test "In production mode, config.serve_static_files is enabled when RAILS_SERVE_STATIC_FILES is set" do
+ test "In production mode, config.public_file_server.enabled is enabled when RAILS_SERVE_STATIC_FILES is set" do
restore_default_config
with_rails_env "production" do
switch_env "RAILS_SERVE_STATIC_FILES", "1" do
app 'production'
- assert app.config.serve_static_files
+ assert app.config.public_file_server.enabled
end
end
end
- test "In production mode, config.serve_static_files is disabled when RAILS_SERVE_STATIC_FILES is blank" do
+ test "In production mode, config.public_file_server.enabled is disabled when RAILS_SERVE_STATIC_FILES is blank" do
restore_default_config
with_rails_env "production" do
switch_env "RAILS_SERVE_STATIC_FILES", " " do
app 'production'
- assert_not app.config.serve_static_files
+ assert_not app.config.public_file_server.enabled
end
end
end
+ test "config.serve_static_files is deprecated" do
+ make_basic_app do |application|
+ assert_deprecated do
+ application.config.serve_static_files = true
+ end
+
+ assert application.config.public_file_server.enabled
+ end
+ end
+
test "config.static_cache_control is deprecated" do
make_basic_app do |application|
assert_deprecated do
@@ -417,6 +427,19 @@ module ApplicationTests
end
end
+ test "raise when secrets.secret_key_base is not a type of string" do
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base: 123
+ YAML
+
+ app 'development'
+
+ assert_raise(ArgumentError) do
+ app.key_generator
+ end
+ end
+
test "prefer secrets.secret_token over config.secret_token" do
app_file 'config/initializers/secret_token.rb', <<-RUBY
Rails.application.config.secret_token = ""
@@ -1354,5 +1377,21 @@ module ApplicationTests
assert_match 'YAML syntax error occurred while parsing', exception.message
end
+
+ test "config_for allows overriding the environment" do
+ app_file 'config/custom.yml', <<-RUBY
+ test:
+ key: 'walrus'
+ production:
+ key: 'unicorn'
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom', env: 'production')
+ RUBY
+ require "#{app_path}/config/environment"
+
+ assert_equal 'unicorn', Rails.application.config.my_custom_config['key']
+ end
end
end
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index dc96480d6d..be86f1a3b8 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do
make_basic_app do |app|
app.config.action_dispatch.x_sendfile_header = 'X-Sendfile'
- app.config.serve_static_files = true
+ app.config.public_file_server.enabled = true
app.paths["public"] = File.join(rails_root, "public")
end
diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb
index 5366537dc2..1246e20d94 100644
--- a/railties/test/application/middleware/static_test.rb
+++ b/railties/test/application/middleware/static_test.rb
@@ -44,7 +44,7 @@ module ApplicationTests
assert_equal 'public, max-age=60', last_response.headers["Cache-Control"]
end
- test "static_index defaults to 'index'" do
+ test "public_file_server.index_name defaults to 'index'" do
app_file "public/index.html", "/index.html"
require "#{app_path}/config/environment"
@@ -54,10 +54,10 @@ module ApplicationTests
assert_equal "/index.html\n", last_response.body
end
- test "static_index configurable" do
+ test "public_file_server.index_name configurable" do
app_file "public/other-index.html", "/other-index.html"
- add_to_config "config.static_index = 'other-index'"
-
+ add_to_config "config.public_file_server.index_name = 'other-index'"
+
require "#{app_path}/config/environment"
get '/'
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 138c63266e..1434522cce 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -155,8 +155,8 @@ module ApplicationTests
assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
- test "removes static asset server if serve_static_files is disabled" do
- add_to_config "config.serve_static_files = false"
+ test "removes static asset server if public_file_server.enabled is disabled" do
+ add_to_config "config.public_file_server.enabled = false"
boot!
assert !middleware.include?("ActionDispatch::Static")
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index e7beab8b5e..f94d08673a 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -49,6 +49,58 @@ module ApplicationTests
db_create_and_drop database_url_db_name
end
+ def with_database_existing
+ Dir.chdir(app_path) do
+ set_database_url
+ `bin/rake db:create`
+ yield
+ `bin/rake db:drop`
+ end
+ end
+
+ test 'db:create failure because database exists' do
+ with_database_existing do
+ output = `bin/rake db:create 2>&1`
+ assert_match /already exists/, output
+ assert_equal 0, $?.exitstatus
+ end
+ end
+
+ def with_bad_permissions
+ Dir.chdir(app_path) do
+ set_database_url
+ FileUtils.chmod("-w", "db")
+ yield
+ FileUtils.chmod("+w", "db")
+ end
+ end
+
+ test 'db:create failure because bad permissions' do
+ with_bad_permissions do
+ output = `bin/rake db:create 2>&1`
+ assert_match /Couldn't create database/, output
+ assert_equal 1, $?.exitstatus
+ end
+ end
+
+ test 'db:drop failure because database does not exist' do
+ Dir.chdir(app_path) do
+ output = `bin/rake db:drop 2>&1`
+ assert_match /does not exist/, output
+ assert_equal 0, $?.exitstatus
+ end
+ end
+
+ test 'db:drop failure because bad permissions' do
+ with_database_existing do
+ with_bad_permissions do
+ output = `bin/rake db:drop 2>&1`
+ assert_match /Couldn't drop/, output
+ assert_equal 1, $?.exitstatus
+ end
+ end
+ end
+
def db_migrate_and_status(expected_database)
Dir.chdir(app_path) do
`bin/rails generate model book title:string;
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 2d8bd7c571..6b74707959 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -154,6 +154,28 @@ module ApplicationTests
end
end
+ test 'running migrations with not timestamp head migration files' do
+ Dir.chdir(app_path) do
+
+ app_file "db/migrate/1_one_migration.rb", <<-MIGRATION
+ class OneMigration < ActiveRecord::Migration
+ end
+ MIGRATION
+
+ app_file "db/migrate/02_two_migration.rb", <<-MIGRATION
+ class TwoMigration < ActiveRecord::Migration
+ end
+ MIGRATION
+
+ `bin/rake db:migrate`
+
+ output = `bin/rake db:migrate:status`
+
+ assert_match(/up\s+001\s+One migration/, output)
+ assert_match(/up\s+002\s+Two migration/, output)
+ end
+ end
+
test 'schema generation when dump_schema_after_migration is set' do
add_to_config('config.active_record.dump_schema_after_migration = false')
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 0aa6ce2252..4965ab7da0 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -344,7 +344,7 @@ module ApplicationTests
create_test_file :models, 'post', pass: false
output = run_test_command('test/models/post_test.rb')
- assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:4}, output
+ assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:6}, output
end
def test_only_inline_failure_output
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
index 46445a001a..cecc3908b3 100644
--- a/railties/test/code_statistics_calculator_test.rb
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -299,6 +299,22 @@ class Animal
assert_equal 0, @code_statistics_calculator.methods
end
+ test 'count rake tasks' do
+ code = <<-'CODE'
+ task :test_task do
+ puts 'foo'
+ end
+
+ CODE
+
+ @code_statistics_calculator.add_by_io(StringIO.new(code), :rake)
+
+ assert_equal 4, @code_statistics_calculator.lines
+ assert_equal 3, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 0, @code_statistics_calculator.methods
+ end
+
private
def temp_file(name, content)
dir = File.expand_path '../fixtures/tmp', __FILE__
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index fabba555ef..b4fbea4af4 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -235,6 +235,21 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/
end
+ def test_route_should_be_idempotent
+ run_generator
+ route_path = File.expand_path('config/routes.rb', destination_root)
+
+ # runs first time, not asserting
+ action :route, "root 'welcome#index'"
+ content_1 = File.read(route_path)
+
+ # runs second time
+ action :route, "root 'welcome#index'"
+ content_2 = File.read(route_path)
+
+ assert_equal content_1, content_2
+ end
+
def test_route_should_add_data_with_an_new_line
run_generator
action :route, "root 'welcome#index'"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index e5f10a89d3..446fef562b 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -264,7 +264,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcmysql-adapter"
else
- assert_gem "mysql2"
+ assert_gem "mysql2", "'>= 0.3.18', '< 0.5'"
end
end
@@ -279,7 +279,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcpostgresql-adapter"
else
- assert_gem "pg"
+ assert_gem "pg", "'~> 0.18'"
end
end
@@ -686,7 +686,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
capture(:stdout) { generator.send(*args, &block) }
end
- def assert_gem(gem)
- assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/
+ def assert_gem(gem, constraint = nil)
+ if constraint
+ assert_file "Gemfile", /^\s*gem\s+["']#{gem}["'], #{constraint}$*/
+ else
+ assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/
+ end
end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index c4ee6602c5..590f06e19a 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -396,4 +396,28 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Stylesheets (should not be removed)
assert_file "app/assets/stylesheets/scaffold.css"
end
+
+ def test_api_scaffold_with_namespace_on_invoke
+ run_generator [ "admin/role", "name:string", "description:string", "--api" ]
+
+ # Model
+ assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/
+ assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/
+ assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/
+ assert_file "test/fixtures/test_app/admin/roles.yml"
+ assert_migration "db/migrate/create_test_app_admin_roles.rb"
+
+ # Route
+ assert_file "config/routes.rb" do |route|
+ assert_match(/^ namespace :admin do\n resources :roles\n end$/, route)
+ end
+
+ # Controller
+ assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content|
+ assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content)
+ assert_match(%r(require_dependency "test_app/application_controller"), content)
+ end
+ assert_file "test/controllers/test_app/admin/roles_controller_test.rb",
+ /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/
+ end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 2c82f728ee..24386de82a 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1205,7 +1205,7 @@ YAML
test "engine can be properly mounted at root" do
add_to_config("config.action_dispatch.show_exceptions = false")
- add_to_config("config.serve_static_files = false")
+ add_to_config("config.public_file_server.enabled = false")
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index fa6bb71c64..e517d8dd0b 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -15,7 +15,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:6$}, @output.string
+ assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:\d+$}, @output.string
assert_rerun_snippet_count 1
end
@@ -51,7 +51,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:6$}, @output.string
+ assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:\d+$}, @output.string
ensure
Rails::TestUnitReporter.executable = original_executable
end
@@ -61,7 +61,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{\A\n\nboo\n\nbin/rails test .*test/test_unit/reporter_test.rb:6\n\n\z}, @output.string
+ assert_match %r{\A\n\nboo\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string
end
test "outputs errors inline" do
@@ -76,7 +76,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
verbose.record(skipped_test)
verbose.report
- assert_match %r{\A\n\nskipchurches, misstemples\n\nbin/rails test .*test/test_unit/reporter_test.rb:6\n\n\z}, @output.string
+ assert_match %r{\A\n\nskipchurches, misstemples\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}, @output.string
end
test "does not output rerun snippets after run" do
diff --git a/tasks/release.rb b/tasks/release.rb
index 2c7e927679..7c8b884d44 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -66,13 +66,24 @@ directory "pkg"
end
namespace :changelog do
+ task :header do
+ (FRAMEWORKS + ['guides']).each do |fw|
+ require 'date'
+ fname = File.join fw, 'CHANGELOG.md'
+
+ header = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n\n* No changes.\n\n\n"
+ contents = header + File.read(fname)
+ File.open(fname, 'wb') { |f| f.write contents }
+ end
+ end
+
task :release_date do
(FRAMEWORKS + ['guides']).each do |fw|
require 'date'
- replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')'
+ replace = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n"
fname = File.join fw, 'CHANGELOG.md'
- contents = File.read(fname).sub(/^([^(]*)\(unreleased\)/, replace)
+ contents = File.read(fname).sub(/^(## Rails .*)\n/, replace)
File.open(fname, 'wb') { |f| f.write contents }
end
end
@@ -98,7 +109,7 @@ namespace :all do
task :push => FRAMEWORKS.map { |f| "#{f}:push" } + ['rails:push']
task :ensure_clean_state do
- unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG'`.strip.empty?
+ unless `git status -s | grep -v 'RAILS_VERSION\\|CHANGELOG\\|Gemfile.lock'`.strip.empty?
abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed"
end
@@ -108,6 +119,10 @@ namespace :all do
end
end
+ task :bundle do
+ sh 'bundle check'
+ end
+
task :commit do
File.open('pkg/commit_message.txt', 'w') do |f|
f.puts "# Preparing for #{version} release\n"
@@ -124,5 +139,5 @@ namespace :all do
sh "git push --tags"
end
- task :release => %w(ensure_clean_state build commit tag push)
+ task :release => %w(ensure_clean_state build bundle commit tag push)
end