aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml11
-rw-r--r--Gemfile11
-rw-r--r--RAILS_VERSION2
-rw-r--r--actionmailer/CHANGELOG.md14
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb2
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb2
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb26
-rw-r--r--actionmailer/test/abstract_unit.rb2
-rw-r--r--actionmailer/test/base_test.rb14
-rw-r--r--actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/on_render.erb7
-rw-r--r--actionmailer/test/message_delivery_test.rb28
-rw-r--r--actionpack/CHANGELOG.md13
-rw-r--r--actionpack/Rakefile5
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb16
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/abstract_unit.rb2
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb29
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb9
-rw-r--r--actionpack/test/dispatch/request_test.rb6
-rw-r--r--actionpack/test/dispatch/response_test.rb34
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb16
-rw-r--r--actionpack/test/lib/controller/fake_models.rb45
-rw-r--r--actionview/CHANGELOG.md29
-rw-r--r--actionview/Rakefile2
-rw-r--r--actionview/actionview.gemspec4
-rw-r--r--actionview/lib/action_view/base.rb4
-rw-r--r--actionview/lib/action_view/buffers.rb7
-rw-r--r--actionview/lib/action_view/gem_version.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb18
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb36
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb7
-rw-r--r--actionview/lib/action_view/test_case.rb3
-rw-r--r--actionview/test/abstract_unit.rb4
-rw-r--r--actionview/test/actionpack/controller/render_test.rb23
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb9
-rw-r--r--actionview/test/fixtures/test/nil_return.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb11
-rw-r--r--actionview/test/template/compiled_templates_test.rb4
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/template/tag_helper_test.rb7
-rw-r--r--actionview/test/template/test_case_test.rb11
-rw-r--r--actionview/test/template/translation_helper_test.rb1
-rw-r--r--activejob/.gitignore1
-rw-r--r--activejob/README.md25
-rw-r--r--activejob/Rakefile83
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb1
-rw-r--r--activejob/lib/active_job/arguments.rb46
-rw-r--r--activejob/lib/active_job/base.rb4
-rw-r--r--activejob/lib/active_job/callbacks.rb12
-rw-r--r--activejob/lib/active_job/configured_job.rb16
-rw-r--r--activejob/lib/active_job/core.rb89
-rw-r--r--activejob/lib/active_job/enqueuing.rb102
-rw-r--r--activejob/lib/active_job/execution.rb32
-rw-r--r--activejob/lib/active_job/gem_version.rb2
-rw-r--r--activejob/lib/active_job/identifier.rb15
-rw-r--r--activejob/lib/active_job/logging.rb38
-rw-r--r--activejob/lib/active_job/queue_adapters.rb34
-rw-r--r--activejob/lib/active_job/queue_adapters/backburner_adapter.rb24
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb14
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb28
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb26
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/resque_adapter.rb25
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb26
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb26
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb27
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb72
-rw-r--r--activejob/lib/active_job/queue_name.rb23
-rw-r--r--activejob/lib/active_job/test_helper.rb30
-rw-r--r--activejob/test/cases/argument_serialization_test.rb84
-rw-r--r--activejob/test/cases/callbacks_test.rb5
-rw-r--r--activejob/test/cases/job_serialization_test.rb2
-rw-r--r--activejob/test/cases/logging_test.rb41
-rw-r--r--activejob/test/cases/parameters_test.rb77
-rw-r--r--activejob/test/cases/queue_naming_test.rb37
-rw-r--r--activejob/test/cases/queuing_test.rb14
-rw-r--r--activejob/test/cases/rescue_test.rb12
-rw-r--r--activejob/test/cases/test_helper_test.rb70
-rw-r--r--activejob/test/helper.rb8
-rw-r--r--activejob/test/integration/queuing_test.rb46
-rw-r--r--activejob/test/jobs/nested_job.rb2
-rw-r--r--activejob/test/jobs/rescue_job.rb2
-rw-r--r--activejob/test/support/integration/adapters/backburner.rb38
-rw-r--r--activejob/test/support/integration/adapters/delayed_job.rb20
-rw-r--r--activejob/test/support/integration/adapters/inline.rb15
-rw-r--r--activejob/test/support/integration/adapters/qu.rb38
-rw-r--r--activejob/test/support/integration/adapters/que.rb37
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb33
-rw-r--r--activejob/test/support/integration/adapters/resque.rb49
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb57
-rw-r--r--activejob/test/support/integration/adapters/sneakers.rb90
-rw-r--r--activejob/test/support/integration/adapters/sucker_punch.rb6
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb21
-rw-r--r--activejob/test/support/integration/helper.rb30
-rw-r--r--activejob/test/support/integration/jobs_manager.rb27
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb48
-rw-r--r--activemodel/CHANGELOG.md19
-rw-r--r--activemodel/Rakefile2
-rw-r--r--activemodel/examples/validations.rb30
-rw-r--r--activemodel/lib/active_model/gem_version.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb8
-rw-r--r--activemodel/test/cases/helper.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb4
-rw-r--r--activerecord/CHANGELOG.md135
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb55
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb7
-rw-r--r--activerecord/lib/active_record/autosave_association.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb31
-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.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb11
-rw-r--r--activerecord/lib/active_record/core.rb10
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb11
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb3
-rw-r--r--activerecord/lib/active_record/reflection.rb7
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb16
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb14
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb48
-rw-r--r--activerecord/lib/active_record/type.rb1
-rw-r--r--activerecord/lib/active_record/type/decorator.rb14
-rw-r--r--activerecord/lib/active_record/type/serialized.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb88
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb4
-rw-r--r--activerecord/test/cases/ar_schema_test.rb5
-rw-r--r--activerecord/test/cases/associations/eager_test.rb10
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/required_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb23
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb16
-rw-r--r--activerecord/test/cases/autosave_association_test.rb24
-rw-r--r--activerecord/test/cases/connection_management_test.rb8
-rw-r--r--activerecord/test/cases/enum_test.rb3
-rw-r--r--activerecord/test/cases/finder_test.rb61
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb16
-rw-r--r--activerecord/test/cases/locking_test.rb25
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb4
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb3
-rw-r--r--activerecord/test/cases/relation/merging_test.rb2
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb51
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb21
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb7
-rw-r--r--activerecord/test/cases/transactions_test.rb31
-rw-r--r--activerecord/test/cases/view_test.rb113
-rw-r--r--activerecord/test/fixtures/developers.yml2
-rw-r--r--activerecord/test/models/comment.rb5
-rw-r--r--activerecord/test/models/parrot.rb2
-rw-r--r--activerecord/test/models/person.rb2
-rw-r--r--activerecord/test/models/personal_legacy_thing.rb4
-rw-r--r--activerecord/test/models/post.rb6
-rw-r--r--activerecord/test/schema/schema.rb8
-rw-r--r--activesupport/CHANGELOG.md106
-rw-r--r--activesupport/lib/active_support.rb10
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb20
-rw-r--r--activesupport/lib/active_support/duration.rb14
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb1
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb1
-rw-r--r--activesupport/lib/active_support/test_case.rb29
-rw-r--r--activesupport/test/abstract_unit.rb2
-rw-r--r--activesupport/test/core_ext/duration_test.rb17
-rw-r--r--activesupport/test/core_ext/module_test.rb11
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb8
-rw-r--r--activesupport/test/inflector_test.rb1
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/message_verifier_test.rb7
-rw-r--r--activesupport/test/test_case_test.rb47
-rwxr-xr-xci/travis.rb14
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md98
-rw-r--r--guides/source/action_controller_overview.md6
-rw-r--r--guides/source/active_job_basics.md88
-rw-r--r--guides/source/active_record_querying.md6
-rw-r--r--guides/source/active_support_core_extensions.md4
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md31
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md17
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/i18n.md16
-rw-r--r--guides/source/maintenance_policy.md9
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md96
-rw-r--r--railties/CHANGELOG.md25
-rw-r--r--railties/lib/rails/application.rb19
-rw-r--r--railties/lib/rails/application/bootstrap.rb3
-rw-r--r--railties/lib/rails/application/configuration.rb7
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb6
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb13
-rw-r--r--railties/lib/rails/commands/server.rb6
-rw-r--r--railties/lib/rails/gem_version.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb7
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb1
-rw-r--r--railties/lib/rails/info.rb29
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb6
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb28
-rw-r--r--railties/lib/rails/test_unit/testing.rake6
-rw-r--r--railties/test/abstract_unit.rb34
-rw-r--r--railties/test/application/configuration_test.rb11
-rw-r--r--railties/test/application/default_stack_test.rb41
-rw-r--r--railties/test/application/middleware_test.rb11
-rw-r--r--railties/test/application/rake/dbs_test.rb7
-rw-r--r--railties/test/application/test_runner_test.rb17
-rw-r--r--railties/test/configuration/middleware_stack_proxy_test.rb1
-rw-r--r--railties/test/generators/app_generator_test.rb5
-rw-r--r--railties/test/generators/plugin_generator_test.rb14
-rw-r--r--railties/test/isolation/abstract_unit.rb4
-rw-r--r--railties/test/rails_info_test.rb13
-rw-r--r--railties/test/railties/engine_test.rb36
-rw-r--r--version.rb2
277 files changed, 3328 insertions, 1411 deletions
diff --git a/.travis.yml b/.travis.yml
index 3d858a5c89..c213700687 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,13 +21,17 @@ env:
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
+ - "GEM=aj:integration"
matrix:
allow_failures:
- rvm: 1.9.3
env: "GEM=ar:mysql"
+ - rvm: 2.0.0
+ env: "GEM=ar:mysql"
+ - rvm: ruby-head
+ env: "GEM=ar:mysql"
- rvm: rbx-2
- rvm: jruby
- - rvm: ruby-head
fast_finish: true
notifications:
email: false
@@ -44,3 +48,8 @@ notifications:
bundler_args: --path vendor/bundle --without test
services:
- memcached
+ - redis
+ - rabbitmq
+addons:
+ postgresql: "9.3"
+
diff --git a/Gemfile b/Gemfile
index 1d135a1af1..7df3cc1cfe 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,16 +2,17 @@ source 'https://rubygems.org'
gemspec
+# We need a newish Rake since Active Job sets its test tasks' descriptions.
+gem 'rake', '>= 10.3'
+
# This needs to be with require false as it is
# loaded after loading the test library to
# ensure correct loading order
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
+gem 'jquery-rails', '~> 4.0.0.beta2'
gem 'coffee-rails', '~> 4.0.0'
-gem 'rails-html-sanitizer', github: 'rails/rails-html-sanitizer'
-gem 'rails-deprecated_sanitizer', github: 'rails/rails-deprecated_sanitizer'
gem 'turbolinks', '~> 2.2.3'
# require: false so bcrypt is loaded only when has_secure_password is used.
@@ -40,12 +41,14 @@ group :job do
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
- gem 'queue_classic', require: false, platforms: :ruby
+ gem 'queue_classic', "< 3.0.0", require: false, platforms: :ruby
gem 'sneakers', '0.1.1.pre', require: false
gem 'que', require: false
gem 'backburner', require: false
gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
gem 'qu-redis', require: false
+ gem 'delayed_job_active_record', require: false
+ gem 'sequel', require: false
end
# Add your own local bundler stuff
diff --git a/RAILS_VERSION b/RAILS_VERSION
index e33286503d..a771777d25 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.2.0.beta1
+4.2.0.beta2
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 1ff4db41a3..e2900c2d10 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,13 +1,19 @@
-* Added #deliver_later, #deliver_now and deprecate #deliver in favour of
- #deliver_now. #deliver_later will enqueue a job to render and deliver
+* Attachments can be added while rendering the mail template.
+
+ Fixes #16974.
+
+ *Christian Felder*
+
+* Added `#deliver_later`, `#deliver_now` and deprecate `#deliver` in favour of
+ `#deliver_now`. `#deliver_later` will enqueue a job to render and deliver
the mail instead of delivering it right at that moment. The job is enqueued
using the new Active Job framework in Rails, and will use whatever queue is
configured for Rails.
*DHH/Abdelkader Boudih/Cristian Bica*
-* Make ActionMailer::Previews methods class methods. Previously they were
- instance methods and ActionMailer tries to render a message when they
+* Make `ActionMailer::Previews` methods class methods. Previously they were
+ instance methods and `ActionMailer` tries to render a message when they
are called.
*Cristian Bica*
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 089e12ebf1..fbf6c5e9f9 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activejob', version
s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.3'
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index f539fc53c6..1ab68d2953 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -775,7 +775,6 @@ module ActionMailer
def mail(headers = {}, &block)
return @_message if @_mail_was_called && headers.blank? && !block
- @_mail_was_called = true
m = @_message
# At the beginning, do not consider class default for content_type
@@ -803,6 +802,8 @@ module ActionMailer
# Render the templates and blocks
responses = collect_responses(headers, &block)
+ @_mail_was_called = true
+
create_parts_from_responses(m, responses)
# Setup content type, reapply charset and handle parts order
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index b9162055f7..0c5c55be84 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -8,7 +8,7 @@ module ActionMailer
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 483277af04..cc7935a7e0 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -29,7 +29,7 @@ module ActionMailer
# Access the message attachments list.
def attachments
- @_message.attachments
+ mailer.attachments
end
# Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index ce416b09d5..7118f9b02c 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -39,13 +39,13 @@ module ActionMailer
# and +raise_delivery_errors+, so use with caution.
#
# Notifier.welcome(User.first).deliver_later!
- # Notifier.welcome(User.first).deliver_later!(in: 1.hour)
- # Notifier.welcome(User.first).deliver_later!(at: 10.hours.from_now)
+ # Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
+ # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
#
# Options:
#
- # * <tt>:in</tt> - Enqueue the email to be delivered with a delay
- # * <tt>:at</tt> - Enqueue the email to be delivered at (after) a specific date / time
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
def deliver_later!(options={})
enqueue_delivery :deliver_now!, options
end
@@ -54,13 +54,13 @@ module ActionMailer
# job runs it will send the email using +deliver_now+.
#
# Notifier.welcome(User.first).deliver_later
- # Notifier.welcome(User.first).deliver_later(in: 1.hour)
- # Notifier.welcome(User.first).deliver_later(at: 10.hours.from_now)
+ # Notifier.welcome(User.first).deliver_later(wait: 1.hour)
+ # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
#
# Options:
#
- # * <tt>:in</tt> - Enqueue the email to be delivered with a delay
- # * <tt>:at</tt> - Enqueue the email to be delivered at (after) a specific date / time
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
def deliver_later(options={})
enqueue_delivery :deliver_now, options
end
@@ -98,15 +98,7 @@ module ActionMailer
def enqueue_delivery(delivery_method, options={})
args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
- enqueue_method = :enqueue
- if options[:at]
- enqueue_method = :enqueue_at
- args.unshift options[:at]
- elsif options[:in]
- enqueue_method = :enqueue_in
- args.unshift options[:in]
- end
- ActionMailer::DeliveryJob.send enqueue_method, *args
+ ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
end
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index ce426cad1e..7681679dc7 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -46,4 +46,4 @@ require 'mocha/setup' # FIXME: stop using mocha
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index d00f5aea47..dcb6959543 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -259,6 +259,20 @@ class BaseTest < ActiveSupport::TestCase
assert_match(/Can't add attachments after `mail` was called./, e.message)
end
+ test "adding inline attachments while rendering mail works" do
+ class LateInlineAttachmentMailer < ActionMailer::Base
+ def on_render
+ mail from: "welcome@example.com", to: "to@example.com"
+ end
+ end
+
+ mail = LateInlineAttachmentMailer.on_render
+ assert_nothing_raised { mail.message }
+
+ assert_equal ["image/jpeg; filename=controller_attachments.jpg",
+ "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map {|a| a['Content-Type'].to_s }
+ end
+
test "accessing attachments works after mail was called" do
class LateAttachmentAccessorMailer < ActionMailer::Base
def welcome
diff --git a/actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/on_render.erb b/actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/on_render.erb
new file mode 100644
index 0000000000..6decd3bb31
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/late_inline_attachment_mailer/on_render.erb
@@ -0,0 +1,7 @@
+<h1>Adding an inline image while rendering</h1>
+
+<% controller.attachments.inline["controller_attachments.jpg"] = 'via controller.attachments.inline' %>
+<%= image_tag attachments['controller_attachments.jpg'].url %>
+
+<% attachments.inline["attachments.jpg"] = 'via attachments.inline' %>
+<%= image_tag attachments['attachments.jpg'].url %>
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 53654871a2..9abf8b225c 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -3,8 +3,10 @@ require 'abstract_unit'
require 'active_job'
require 'minitest/mock'
require 'mailers/delayed_mailer'
+require 'active_support/core_ext/numeric/time'
class MessageDeliveryTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
setup do
@previous_logger = ActiveJob::Base.logger
@@ -13,6 +15,8 @@ class MessageDeliveryTest < ActiveSupport::TestCase
ActiveJob::Base.logger = Logger.new(nil)
@mail = DelayedMailer.test_message(1, 2, 3)
ActionMailer::Base.deliveries.clear
+ ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
+ ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
end
teardown do
@@ -70,33 +74,31 @@ class MessageDeliveryTest < ActiveSupport::TestCase
ActionMailer::Base.deliveries.clear
end
- test 'should enqueue the email with :deliver delivery method' do
- ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
+ test 'should enqueue the email with :deliver_now delivery method' do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later
end
- assert_equal ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
end
- test 'should enqueue the email with :deliver! delivery method' do
- ret = ActionMailer::DeliveryJob.stub :enqueue, ->(*args){ args } do
+ test 'should enqueue the email with :deliver_now! delivery method' do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do
@mail.deliver_later!
end
- assert_equal ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3], ret
end
test 'should enqueue a delivery with a delay' do
- ret = ActionMailer::DeliveryJob.stub :enqueue_in, ->(*args){ args } do
- @mail.deliver_later in: 600
+ travel_to Time.new(2004, 11, 24, 01, 04, 44) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
+ @mail.deliver_later wait: 600.seconds
+ end
end
- assert_equal [600, 'DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
end
test 'should enqueue a delivery at a specific time' do
- later_time = Time.now.to_i + 3600
- ret = ActionMailer::DeliveryJob.stub :enqueue_at, ->(*args){ args } do
- @mail.deliver_later at: later_time
+ later_time = Time.now.to_f + 3600
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
+ @mail.deliver_later wait_until: later_time
end
- assert_equal [later_time, 'DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], ret
end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index e250450a76..de9722c392 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,16 @@
+* Deprecate implicit Array conversion for Response objects. It was added
+ (using `#to_ary`) so we could conveniently use implicit splatting:
+
+ status, headers, body = response
+
+ But it also means `response + response` works and `[response].flatten`
+ cascades down to the Rack body. Nonsense behavior. Instead, rely on
+ explicit conversion and splatting with `#to_a`:
+
+ status, header, body = *response
+
+ *Jeremy Kemper*
+
* Don't rescue `IPAddr::InvalidAddressError`.
`IPAddr::InvalidAddressError` does not exist in Ruby 1.9.3
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index cf20751687..af075f82ad 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -9,7 +9,10 @@ task :default => :test
# Run the unit tests
Rake::TestTask.new do |t|
t.libs << 'test'
- t.test_files = test_files
+
+ # make sure we include the tests in alphabetical order as on some systems
+ # this will not happen automatically and the tests (as a whole) will error
+ t.test_files = test_files.sort
t.warning = true
t.verbose = true
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index a39b3e86d4..69a160fdc8 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -23,8 +23,8 @@ Gem::Specification.new do |s|
s.add_dependency 'rack', '~> 1.6.0.beta'
s.add_dependency 'rack-test', '~> 0.6.2'
- s.add_dependency 'rails-deprecated_sanitizer', '~> 1.0', '>= 1.0.2'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2'
+ s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.3'
s.add_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 02c4e563f5..bc94536c8c 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -34,14 +34,15 @@ module ActionController
end
def render_to_body(options)
- _handle_render_options(options) || super
+ _render_to_body_with_renderer(options) || super
end
- def _handle_render_options(options)
+ def _render_to_body_with_renderer(options)
_renderers.each do |name|
if options.key?(name)
_process_options(options)
- return send("_render_option_#{name}", options.delete(name), options)
+ method_name = Renderers._render_with_renderer_method_name(name)
+ return send(method_name, options.delete(name), options)
end
end
nil
@@ -51,6 +52,10 @@ module ActionController
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
RENDERERS = Set.new
+ def self._render_with_renderer_method_name(key)
+ "_render_with_renderer_#{key}"
+ end
+
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
# <tt>AbstractController::Rendering#render</tt>. To create a renderer
@@ -84,7 +89,7 @@ module ActionController
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
# <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
- define_method("_render_option_#{key}", &block)
+ define_method(_render_with_renderer_method_name(key), &block)
RENDERERS << key.to_sym
end
@@ -95,8 +100,8 @@ module ActionController
# ActionController::Renderers.remove(:csv)
def self.remove(key)
RENDERERS.delete(key.to_sym)
- method = "_render_option_#{key}"
- remove_method(method) if method_defined?(method)
+ method_name = _render_with_renderer_method_name(key)
+ remove_method(method_name) if method_defined?(method_name)
end
module All
@@ -112,7 +117,7 @@ module ActionController
json = json.to_json(options) unless json.kind_of?(String)
if options[:callback].present?
- if self.content_type.nil? || self.content_type == Mime::JSON
+ if content_type.nil? || content_type == Mime::JSON
self.content_type = Mime::JS
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 9450be838c..b9d5009683 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -45,8 +45,8 @@ module Mime
#
# respond_to do |format|
# format.html
- # format.ics { render text: post.to_ics, mime_type: Mime::Type["text/calendar"] }
- # format.xml { render xml: @people }
+ # format.ics { render text: @post.to_ics, mime_type: Mime::Type["text/calendar"] }
+ # format.xml { render xml: @post }
# end
# end
# end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index c441f7f95b..e854dd266c 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -298,7 +298,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
@env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
- rescue TypeError, Rack::Utils::InvalidParameterError => e
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e)
end
alias :query_parameters :GET
@@ -306,7 +306,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
@env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
- rescue TypeError, Rack::Utils::InvalidParameterError => e
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
alias :request_parameters :POST
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 2fab6be1a5..c5e18048da 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/deprecation'
require 'action_dispatch/http/filter_redirect'
require 'monitor'
@@ -274,12 +275,22 @@ module ActionDispatch # :nodoc:
end
# Turns the Response into a Rack-compatible array of the status, headers,
- # and body.
+ # and body. Allows explict splatting:
+ #
+ # status, headers, body = *response
def to_a
rack_response @status, @header.to_hash
end
alias prepare! to_a
- alias to_ary to_a
+
+ # Be super clear that a response object is not an Array. Defining this
+ # would make implicit splatting work, but it also makes adding responses
+ # as arrays work, and "flattening" responses, cascading to the rack body!
+ # Not sensible behavior.
+ def to_ary
+ ActiveSupport::Deprecation.warn 'ActionDispatch::Response#to_ary no longer performs implicit conversion to an Array. Please use response.to_a instead, or a splat like `status, headers, body = *response`'
+ to_a
+ end
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
@@ -369,6 +380,10 @@ module ActionDispatch # :nodoc:
def to_path
@response.stream.to_path
end
+
+ def to_ary
+ nil
+ end
end
def rack_response(status, header)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index f15868d37e..0847842fa2 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -116,7 +116,6 @@ module ActionDispatch
action,
type,
opts
-
end
# Returns the path component of a URL for the given record. It uses
@@ -159,8 +158,7 @@ module ActionDispatch
end
def polymorphic_path_for_action(action, record_or_hash, options)
- options = options.merge(:action => action, :routing_type => :path)
- polymorphic_path(record_or_hash, options)
+ polymorphic_path(record_or_hash, options.merge(:action => action))
end
class HelperMethodBuilder # :nodoc:
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 2cf38a9c2d..e06f7037c6 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -38,7 +38,7 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, msg=nil)
- request = recognized_request_for(path, extras)
+ request = recognized_request_for(path, extras, msg)
expected_options = expected_options.clone
@@ -69,9 +69,9 @@ module ActionDispatch
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
- def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
+ def assert_generates(expected_path, options, defaults={}, extras={}, message=nil)
if expected_path =~ %r{://}
- fail_on(URI::InvalidURIError) do
+ fail_on(URI::InvalidURIError, message) do
uri = URI.parse(expected_path)
expected_path = uri.path.to_s.empty? ? "/" : uri.path
end
@@ -174,7 +174,7 @@ module ActionDispatch
private
# Recognizes the route for a given path.
- def recognized_request_for(path, extras = {})
+ def recognized_request_for(path, extras = {}, msg)
if path.is_a?(Hash)
method = path[:method]
path = path[:path]
@@ -186,7 +186,7 @@ module ActionDispatch
request = ActionController::TestRequest.new
if path =~ %r{://}
- fail_on(URI::InvalidURIError) do
+ fail_on(URI::InvalidURIError, msg) do
uri = URI.parse(path)
request.env["rack.url_scheme"] = uri.scheme || "http"
request.host = uri.host if uri.host
@@ -200,7 +200,7 @@ module ActionDispatch
request.request_method = method if method
- params = fail_on(ActionController::RoutingError) do
+ params = fail_on(ActionController::RoutingError, msg) do
@routes.recognize_path(path, { :method => method, :extras => extras })
end
request.path_parameters = params.with_indifferent_access
@@ -208,10 +208,10 @@ module ActionDispatch
request
end
- def fail_on(exception_class)
+ def fail_on(exception_class, message)
yield
rescue exception_class => e
- raise Minitest::Assertion, e.message
+ raise Minitest::Assertion, message || e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 19eca60f70..7361e6c44b 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -1,3 +1,3 @@
require 'active_support/deprecation'
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been has been extracted to the rails-dom-testing gem.") \ No newline at end of file
+ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.")
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
index d5348d80e1..5c2905d1ac 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
@@ -1,3 +1,3 @@
require 'active_support/deprecation'
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::TagAssertions has been has been extracted to the rails-dom-testing gem.")
+ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::TagAssertions has been extracted to the rails-dom-testing gem.")
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 280d35adcb..6834845d4f 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -8,7 +8,7 @@ module ActionPack
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 799c17119d..69312e4c22 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -516,4 +516,4 @@ end
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index a961cbf849..0c21bb0719 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -13,15 +13,6 @@ module RenderFile
render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
end
- def without_file_key
- render File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
- end
-
- def without_file_key_with_instance_variable
- @secret = 'in the sauce'
- render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
- end
-
def relative_path
@secret = 'in the sauce'
render :file => '../../fixtures/test/render_file_with_ivar'
@@ -41,11 +32,6 @@ module RenderFile
path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')
render :file => path, :locals => {:secret => 'in the sauce'}
end
-
- def without_file_key_with_locals
- path = FIXTURES.join('test/render_file_with_locals').to_s
- render path, :locals => {:secret => 'in the sauce'}
- end
end
class TestBasic < Rack::TestCase
@@ -61,16 +47,6 @@ module RenderFile
assert_response "The secret is in the sauce\n"
end
- test "rendering path without specifying the :file key" do
- get :without_file_key
- assert_response "Hello world!"
- end
-
- test "rendering path without specifying the :file key with ivar" do
- get :without_file_key_with_instance_variable
- assert_response "The secret is in the sauce\n"
- end
-
test "rendering a relative path" do
get :relative_path
assert_response "The secret is in the sauce\n"
@@ -90,10 +66,5 @@ module RenderFile
get :with_locals
assert_response "The secret is in the sauce\n"
end
-
- test "rendering path without specifying the :file key with locals" do
- get :without_file_key_with_locals
- assert_response "The secret is in the sauce\n"
- end
end
end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index e87811776a..42a86b1d0d 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -45,6 +45,10 @@ module RenderTemplate
render :template => "locals", :locals => { :secret => 'area51' }
end
+ def with_locals_without_key
+ render "locals", :locals => { :secret => 'area51' }
+ end
+
def builder_template
render :template => "xml_template"
end
@@ -101,6 +105,11 @@ module RenderTemplate
assert_response "The secret is area51"
end
+ test "rendering a template with local variables without key" do
+ get :with_locals
+ assert_response "The secret is area51"
+ end
+
test "rendering a builder template" do
get :builder_template, "format" => "xml"
assert_response "<html>\n <p>Hello</p>\n</html>\n"
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 00b80c7357..a58306ea0a 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -915,7 +915,7 @@ class RequestParameters < BaseRequestTest
2.times do
assert_raises(ActionController::BadRequest) do
- # rack will raise a TypeError when parsing this query string
+ # rack will raise a Rack::Utils::ParameterTypeError when parsing this query string
request.parameters
end
end
@@ -941,7 +941,7 @@ class RequestParameters < BaseRequestTest
)
assert_raises(ActionController::BadRequest) do
- # rack will raise a TypeError when parsing this query string
+ # rack will raise a Rack::Utils::ParameterTypeError when parsing this query string
request.parameters
end
end
@@ -950,7 +950,7 @@ class RequestParameters < BaseRequestTest
request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
e = assert_raises(ActionController::BadRequest) do
- # rack will raise a TypeError when parsing this query string
+ # rack will raise a Rack::Utils::ParameterTypeError when parsing this query string
request.parameters
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 187b9a2420..48342e252a 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -1,4 +1,6 @@
require 'abstract_unit'
+require 'timeout'
+require 'rack/content_length'
class ResponseTest < ActiveSupport::TestCase
def setup
@@ -220,9 +222,9 @@ class ResponseTest < ActiveSupport::TestCase
assert @response.respond_to?(:method_missing, true)
end
- test "can be destructured into status, headers and an enumerable body" do
+ test "can be explicitly destructured into status, headers and an enumerable body" do
response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
- status, headers, body = response
+ status, headers, body = *response
assert_equal 404, status
assert_equal({ 'Content-Type' => 'text/plain' }, headers)
@@ -231,12 +233,38 @@ class ResponseTest < ActiveSupport::TestCase
test "[response].flatten does not recurse infinitely" do
Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
- status, headers, body = [@response].flatten
+ status, headers, body = assert_deprecated { [@response].flatten }
assert_equal @response.status, status
assert_equal @response.headers, headers
assert_equal @response.body, body.each.to_a.join
end
end
+
+ test "compatibility with Rack::ContentLength" do
+ @response.body = 'Hello'
+ app = lambda { |env| @response.to_a }
+ env = Rack::MockRequest.env_for("/")
+
+ status, headers, body = app.call(env)
+ assert_nil headers['Content-Length']
+
+ status, headers, body = Rack::ContentLength.new(app).call(env)
+ assert_equal '5', headers['Content-Length']
+ end
+
+ test "implicit destructuring and Array conversion is deprecated" do
+ response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
+
+ assert_deprecated do
+ status, headers, body = response
+
+ assert_equal 404, status
+ assert_equal({ 'Content-Type' => 'text/plain' }, headers)
+ assert_equal ['Not Found'], body.each.to_a
+ end
+
+ assert_deprecated { response.to_ary }
+ end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
index aea4489852..56ea644f22 100644
--- a/actionpack/test/dispatch/routing_assertions_test.rb
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -74,10 +74,26 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' })
end
+ def test_assert_recognizes_raises_message
+ err = assert_raise(Assertion) do
+ assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles', {}, "This is a really bad msg")
+ end
+
+ assert_match err.message, "This is a really bad msg"
+ end
+
def test_assert_routing
assert_routing('/articles', :controller => 'articles', :action => 'index')
end
+ def test_assert_routing_raises_message
+ err = assert_raise(Assertion) do
+ assert_routing('/thisIsNotARoute', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' }, {}, "This is a really bad msg")
+ end
+
+ assert_match err.message, "This is a really bad msg"
+ end
+
def test_assert_routing_with_defaults
assert_routing('/articles/1/edit', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' })
end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index b8b51d86c2..ce9522d12a 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -28,30 +28,6 @@ class Customer < Struct.new(:name, :id)
end
end
-class ValidatedCustomer < Customer
- def errors
- if name =~ /Sikachu/i
- []
- else
- [{:name => "is invalid"}]
- end
- end
-end
-
-module Quiz
- class Question < Struct.new(:name, :id)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- def persisted?
- id.present?
- end
- end
-
- class Store < Question
- end
-end
-
class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -95,24 +71,3 @@ class Comment
attr_accessor :body
end
-
-module Blog
- def self.use_relative_model_naming?
- true
- end
-
- class Post < Struct.new(:title, :id)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- def persisted?
- id.present?
- end
- end
-end
-
-class RenderJsonTestException < Exception
- def as_json(options = nil)
- { :error => self.class.name, :message => self.to_s }
- end
-end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index f5c520937c..8ac74dc938 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,24 @@
+* Changed the meaning of `render "foo/bar"`.
+
+ Previously, calling `render "foo/bar"` in a controller action is equivalent
+ to `render file: "foo/bar"`. In Rails 4.2, this has been changed to mean
+ `render template: "foo/bar"` instead. If you need to render a file, please
+ change your code to use the explicit form (`render file: "foo/bar"`) instead.
+
+ *Jeremy Jackson*
+
+* Add support for ARIA attributes in tags.
+
+ Example:
+
+ <%= f.text_field :name, aria: { required: "true", hidden: "false" } %>
+
+ now generates:
+
+ <input aria-hidden="false" aria-required="true" id="user_name" name="user[name]" type="text">
+
+ *Paola Garcia Casadiego*
+
* Provide a `builder` object when using the `label` form helper in block form.
The new `builder` object responds to `translation`, allowing I18n fallback support
@@ -115,12 +136,12 @@
Before:
- #=> favicon_link_tag 'myicon.ico'
+ # => favicon_link_tag 'myicon.ico'
<link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
After:
- #=> favicon_link_tag 'myicon.ico'
+ # => favicon_link_tag 'myicon.ico'
<link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
*Geoffroy Lorieux*
@@ -133,7 +154,7 @@
*Joost Baaij*
-* `collection_check_boxes` respects `:index` option for the hidden filed name.
+* `collection_check_boxes` respects `:index` option for the hidden field name.
Fixes #14147.
@@ -154,7 +175,7 @@
*Vasiliy Ermolovich*
-* Fixed a problem where the default options for the `button_tag` helper is not
+* Fixed a problem where the default options for the `button_tag` helper are not
applied correctly.
Fixes #14254.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 3a8bcc4dfe..d56fe9ea76 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -18,7 +18,7 @@ namespace :test do
Rake::TestTask.new(:template) do |t|
t.libs << 'test'
- t.test_files = Dir.glob('test/template/**/*_test.rb')
+ t.test_files = Dir.glob('test/template/**/*_test.rb').sort
t.warning = true
t.verbose = true
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 565c22e1e8..e43c19b018 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -23,8 +23,8 @@ Gem::Specification.new do |s|
s.add_dependency 'builder', '~> 3.1'
s.add_dependency 'erubis', '~> 2.7.0'
- s.add_dependency 'rails-deprecated_sanitizer', '~> 1.0', '>= 1.0.2'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2'
+ s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.3'
s.add_development_dependency 'actionpack', version
s.add_development_dependency 'activemodel', version
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index d1bade0d3d..3071a13655 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -33,7 +33,9 @@ module ActionView #:nodoc:
#
# If you absolutely must write from within a function use +concat+.
#
- # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
+ # When on a line that only contains whitespaces except for the tag, <% %> suppress leading and trailing whitespace,
+ # including the trailing newline. <% %> and <%- -%> are the same.
+ # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces.
#
# === Using sub templates
#
diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb
index 361a0dccbe..be5d86b1dc 100644
--- a/actionview/lib/action_view/buffers.rb
+++ b/actionview/lib/action_view/buffers.rb
@@ -13,10 +13,11 @@ module ActionView
end
alias :append= :<<
- def safe_concat(value)
- return self if value.nil?
- super(value.to_s)
+ def safe_expr_append=(val)
+ return self if val.nil?
+ safe_concat val.to_s
end
+
alias :safe_append= :safe_concat
end
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 1f506866e9..076b8ec30b 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -8,7 +8,7 @@ module ActionView
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 9e8d005ec7..29733442c1 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -121,8 +121,8 @@ module ActionView
# asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
- return "" unless source.present?
source = source.to_s
+ return "" unless source.present?
return source if source =~ URI_REGEXP
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 09843ca70d..038f9e384b 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -51,9 +51,7 @@ module ActionView
# The HTML generated for this would be (modulus formatting):
#
# <form action="/people" class="new_person" id="new_person" method="post">
- # <div style="display:none">
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
- # </div>
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# <label for="person_first_name">First name</label>:
# <input id="person_first_name" name="person[first_name]" type="text" /><br />
#
@@ -81,10 +79,8 @@ module ActionView
# the code above as is would yield instead:
#
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
- # <div style="display:none">
- # <input name="_method" type="hidden" value="patch" />
- # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
- # </div>
+ # <input name="_method" type="hidden" value="patch" />
+ # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# <label for="person_first_name">First name</label>:
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
#
@@ -315,9 +311,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-remote='true'>
- # <div style='display:none'>
- # <input name='_method' type='hidden' value='patch' />
- # </div>
+ # <input name='_method' type='hidden' value='patch' />
# ...
# </form>
#
@@ -333,9 +327,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
- # <div style='display:none'>
- # <input name='_method' type='hidden' value='patch' />
- # </div>
+ # <input name='_method' type='hidden' value='patch' />
# ...
# </form>
#
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 394250f058..4f2db0a0c4 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/object/try'
require 'active_support/deprecation'
-require 'rails-deprecated_sanitizer'
+require 'rails-html-sanitizer'
module ActionView
# = Action View Sanitize Helpers
@@ -122,14 +122,9 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # This uses html-scanner for the HTML sanitization.
- # In the next Rails version this will use Rails::Html::Sanitizer instead.
- # To get this new behavior now, in your Gemfile, add:
- #
- # gem 'rails-html-sanitizer'
- #
+ # Provided strictly for compabitility and can be removed in Rails 5.
def sanitizer_vendor
- Rails::DeprecatedSanitizer
+ Rails::Html::Sanitizer
end
def sanitized_allowed_tags
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 268558669e..c20800598f 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -20,6 +20,8 @@ module ActionView
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
+
PRE_CONTENT_STRINGS = {
:textarea => "\n"
}
@@ -148,9 +150,9 @@ module ActionView
return if options.blank?
attrs = []
options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
+ if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
- attrs << data_tag_option(k, v, escape)
+ attrs << prefix_tag_option(key, k, v, escape)
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
attrs << boolean_tag_option(key) if value
@@ -161,8 +163,8 @@ module ActionView
" #{attrs.sort! * ' '}" unless attrs.empty?
end
- def data_tag_option(key, value, escape)
- key = "data-#{key.to_s.dasherize}"
+ def prefix_tag_option(prefix, key, value, escape)
+ key = "#{prefix}-#{key.to_s.dasherize}"
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
value = value.to_json
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 1d50ea2ff5..c2fda42396 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -6,6 +6,7 @@ module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
+ include TagHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
# First, it will ensure that any thrown +MissingTranslation+ messages will be turned
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index c3be47133c..364414da05 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -229,59 +229,51 @@ module ActionView
# ==== Examples
# <%= button_to "New", action: "new" %>
# # => "<form method="post" action="/controller/new" class="button_to">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
# <%= button_to "New", new_articles_path %>
# # => "<form method="post" action="/articles/new" class="button_to">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
# <%= button_to [:make_happy, @user] do %>
# Make happy <strong><%= @user.name %></strong>
# <% end %>
# # => "<form method="post" action="/users/1/make_happy" class="button_to">
- # # <div>
- # # <button type="submit">
- # # Make happy <strong><%= @user.name %></strong>
- # # </button>
- # # </div>
+ # # <button type="submit">
+ # # Make happy <strong><%= @user.name %></strong>
+ # # </button>
# # </form>"
#
# <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
- # # <div><input value="New" type="submit" /></div>
+ # # <input value="New" type="submit" />
# # </form>"
#
#
# <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div>
- # # <input value="Create" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input value="Create" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
#
#
# <%= button_to "Delete Image", { action: "delete", id: @image.id },
# method: :delete, data: { confirm: "Are you sure?" } %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
- # # <div>
- # # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input type="hidden" name="_method" value="delete" />
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
#
#
# <%= button_to('Destroy', 'http://www.example.com',
# method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
- # # <div>
- # # <input name='_method' value='delete' type='hidden' />
- # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
- # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
- # # </div>
+ # # <input name='_method' value='delete' type='hidden' />
+ # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </form>"
# #
def button_to(name = nil, options = nil, html_options = nil, &block)
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 81d5836a8c..5cbdfdf6c0 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -108,7 +108,7 @@ module ActionView
end
# Normalize args by converting render "foo" to render :action => "foo" and
- # render "foo/bar" to render :file => "foo/bar".
+ # render "foo/bar" to render :template => "foo/bar".
# :api: private
def _normalize_args(action=nil, options={})
options = super(action, options)
@@ -118,7 +118,7 @@ module ActionView
options = action
when String, Symbol
action = action.to_s
- key = action.include?(?/) ? :file : :action
+ key = action.include?(?/) ? :template : :action
options[key] = action
else
options[:partial] = action
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 4523060442..3c2224fbf5 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -49,9 +49,9 @@ module ActionView
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
- src << "@output_buffer.safe_append= " << code
+ src << "@output_buffer.safe_expr_append= " << code
else
- src << "@output_buffer.safe_append=(" << code << ");"
+ src << "@output_buffer.safe_expr_append=(" << code << ");"
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index d29d020c17..d77421d5f5 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -272,13 +272,13 @@ module ActionView
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
#
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
+ # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
#
# This one allows you to keep files with different formats in separate subdirectories,
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
#
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
+ # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
#
# If you don't specify a pattern then the default will be used.
#
@@ -287,7 +287,7 @@ module ActionView
#
# ActionController::Base.view_paths = FileSystemResolver.new(
# Rails.root.join("app/views"),
- # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
+ # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}"
# )
#
# ==== Pattern format and variables
@@ -299,6 +299,7 @@ module ActionView
# * <tt>:action</tt> - name of the action
# * <tt>:locale</tt> - possible locale versions
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
+ # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
#
class FileSystemResolver < PathResolver
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 7edfc436a6..af34d2ce5a 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -158,8 +158,7 @@ module ActionView
# Need to experiment if this priority is the best one: rendered => output_buffer
def document_root_element
- @html_document ||= Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered)
- @html_document.root
+ Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
end
def say_no_to_protect_against_forgery!
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 923e637f11..7096d588b6 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -16,11 +16,9 @@ silence_warnings do
end
require 'active_support/testing/autorun'
-require 'abstract_controller'
require 'action_controller'
require 'action_view'
require 'action_view/testing/resolvers'
-require 'action_dispatch'
require 'active_support/dependencies'
require 'active_model'
require 'active_record'
@@ -344,4 +342,4 @@ require 'mocha/setup' # FIXME: stop using mocha
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index b3b51ae583..563caee8a2 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -91,17 +91,17 @@ class TestController < ApplicationController
# :ported:
def render_hello_world
- render :template => "test/hello_world"
+ render "test/hello_world"
end
def render_hello_world_with_last_modified_set
response.last_modified = Date.new(2008, 10, 10).to_time
- render :template => "test/hello_world"
+ render "test/hello_world"
end
# :ported: compatibility
def render_hello_world_with_forward_slash
- render :template => "/test/hello_world"
+ render "/test/hello_world"
end
# :ported:
@@ -111,7 +111,7 @@ class TestController < ApplicationController
# :deprecated:
def render_template_in_top_directory_with_slash
- render :template => '/shared'
+ render '/shared'
end
# :ported:
@@ -160,13 +160,6 @@ class TestController < ApplicationController
end
# :ported:
- def render_file_as_string_with_instance_variables
- @secret = 'in the sauce'
- path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar'))
- render path
- end
-
- # :ported:
def render_file_not_using_full_path
@secret = 'in the sauce'
render :file => 'test/render_file_with_ivar'
@@ -194,7 +187,7 @@ class TestController < ApplicationController
def render_file_as_string_with_locals
path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals'))
- render path, :locals => {:secret => 'in the sauce'}
+ render file: path, :locals => {:secret => 'in the sauce'}
end
def accessing_request_in_template
@@ -794,12 +787,6 @@ class RenderTest < ActionController::TestCase
end
# :ported:
- def test_render_file_as_string_with_instance_variables
- get :render_file_as_string_with_instance_variables
- assert_equal "The secret is in the sauce\n", @response.body
- end
-
- # :ported:
def test_render_file_not_using_full_path
get :render_file_not_using_full_path
assert_equal "The secret is in the sauce\n", @response.body
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 4e94304796..5842b775bb 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -282,6 +282,15 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_regression_path_helper_prefixed_with_new_and_edit
+ with_test_routes do
+ assert_equal "/projects/new", new_polymorphic_path(@project)
+
+ @project.save
+ assert_equal "/projects/#{@project.id}/edit", edit_polymorphic_path(@project)
+ end
+ end
+
def test_url_helper_prefixed_with_edit
with_test_routes do
@project.save
diff --git a/actionview/test/fixtures/test/nil_return.erb b/actionview/test/fixtures/test/nil_return.erb
new file mode 100644
index 0000000000..90ce3881f6
--- /dev/null
+++ b/actionview/test/fixtures/test/nil_return.erb
@@ -0,0 +1 @@
+This is nil: <%== nil %>
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index d789a5ca27..df8547ef85 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -535,6 +535,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal copy, source
end
+ class PlaceholderImage
+ def blank?; true; end
+ def to_s; 'no-image-yet.png'; end
+ end
+ def test_image_tag_with_blank_placeholder
+ assert_equal '<img alt="" src="/images/no-image-yet.png" />', image_tag(PlaceholderImage.new, alt: "")
+ end
+ def test_image_path_with_blank_placeholder
+ assert_equal '/images/no-image-yet.png', image_path(PlaceholderImage.new)
+ end
+
def test_image_path_with_asset_host_proc_returning_nil
@controller.config.asset_host = Proc.new do |source|
unless source.end_with?("tiff")
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index b84aca6746..f6c1283b92 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -5,6 +5,10 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
ActionView::LookupContext::DetailsKey.clear
end
+ def test_template_with_nil_erb_return
+ assert_equal "This is nil: \n", render(:template => "test/nil_return")
+ end
+
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal "one", render(:file => "test/render_file_with_locals_and_default")
assert_equal "two", render(:file => "test/render_file_with_locals_and_default", :locals => { :secret => "two" })
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index a27258a870..e4be21be2c 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -18,7 +18,7 @@ class SanitizeHelperTest < ActionView::TestCase
def test_should_sanitize_illegal_style_properties
raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
- expected = %(display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;)
+ expected = %(display: block; width: 100%; height: 100%; background-color: black; background-x: center; background-y: center;)
assert_equal expected, sanitize_css(raw)
end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index 0ea669b3d0..ce89d5728e 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -156,4 +156,11 @@ class TagHelperTest < ActionView::TestCase
tag('a', { data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: 'hello', symbol: :foo, array: [1, 2, 3], hash: { key: 'value'}, string_with_quotes: 'double"quote"party"' } })
}
end
+
+ def test_aria_attributes
+ ['aria', :aria].each { |aria|
+ assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{&quot;key&quot;:&quot;value&quot;}" aria-string-with-quotes="double&quot;quote&quot;party&quot;" aria-string="hello" aria-symbol="foo" />',
+ tag('a', { aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: 'hello', symbol: :foo, array: [1, 2, 3], hash: { key: 'value'}, string_with_quotes: 'double"quote"party"' } })
+ }
+ end
end
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 4582fa13ee..5697ffa317 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -293,6 +293,17 @@ module ActionView
assert_select 'li', :text => 'foo'
end
end
+
+ test "do not memoize the document_root_element in view tests" do
+ concat form_tag('/foo')
+
+ assert_select 'form'
+
+ concat content_tag(:b, 'Strong', class: 'foo')
+
+ assert_select 'form'
+ assert_select 'b.foo'
+ end
end
class RenderTemplateTest < ActionView::TestCase
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 41f6770f23..362f05ea70 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -1,7 +1,6 @@
require 'abstract_unit'
class TranslationHelperTest < ActiveSupport::TestCase
- include ActionView::Helpers::TagHelper
include ActionView::Helpers::TranslationHelper
attr_reader :request, :view
diff --git a/activejob/.gitignore b/activejob/.gitignore
new file mode 100644
index 0000000000..b3aaf55871
--- /dev/null
+++ b/activejob/.gitignore
@@ -0,0 +1 @@
+test/dummy
diff --git a/activejob/README.md b/activejob/README.md
index 1f300fcf62..b5d27272b1 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -24,9 +24,9 @@ Set the queue adapter for Active Job:
``` ruby
ActiveJob::Base.queue_adapter = :inline # default queue adapter
-# Adapters currently supported: :backburner, :delayed_job, :qu, :que, :queue_classic,
-# :resque, :sidekiq, :sneakers, :sucker_punch
```
+Note: To learn how to use your preferred queueing backend see its adapter
+documentation at ActiveJob::QueueAdapters.
Declare a job like so:
@@ -43,15 +43,15 @@ end
Enqueue a job like so:
```ruby
-MyJob.enqueue 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 the queueing system is free.
```
```ruby
-MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
+MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) # Enqueue a job to be performed tomorrow at noon.
```
```ruby
-MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
+MyJob.set(wait: 1.week).perform_later(record) # Enqueue a job to be performed 1 week from now.
```
That's it!
@@ -88,18 +88,9 @@ by default has been mixed into Active Record classes.
## Supported queueing systems
-We currently have adapters for:
-
-* [Backburner](https://github.com/nesquena/backburner)
-* [Delayed Job](https://github.com/collectiveidea/delayed_job)
-* [Qu](https://github.com/bkeepers/qu)
-* [Que](https://github.com/chanks/que)
-* [QueueClassic](https://github.com/ryandotsmith/queue_classic)
-* [Resque 1.x](https://github.com/resque/resque)
-* [Sidekiq](https://github.com/mperham/sidekiq)
-* [Sneakers](https://github.com/jondot/sneakers)
-* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
-
+Active Job has built-in adapters for multiple queueing backends (Sidekiq,
+Resque, Delayed Job and others). To get an up-to-date list of the adapters
+see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
## Auxiliary gems
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 484cd1d0b8..dadd0baf82 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,73 +1,74 @@
require 'rake/testtask'
require 'rubygems/package_task'
-dir = File.dirname(__FILE__)
-
-def run_without_aborting(*tasks)
- errors = []
-
- tasks.each do |task|
- begin
- Rake::Task[task].invoke
- rescue Exception
- errors << task
- end
- end
-
- abort "Errors running #{errors.join(', ')}" if errors.any?
-end
-
-task default: :test
-
ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
-desc 'Run all adapter tests'
-task :test do
- tasks = ACTIVEJOB_ADAPTERS.map{|a| "test_#{a}" }
- run_without_aborting(*tasks)
-end
+task default: :test
+task test: 'test:default'
namespace :test do
+ desc 'Run all adapter tests'
+ task :default do
+ run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:#{a}" }
+ end
+
desc 'Run all adapter tests in isolation'
task :isolated do
- tasks = ACTIVEJOB_ADAPTERS.map{|a| "isolated_test_#{a}" }
- run_without_aborting(*tasks)
+ run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:isolated:#{a}" }
+ end
+
+ desc 'Run integration tests for all adapters'
+ task :integration do
+ run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:integration:#{a}" }
+ end
+
+ task 'env:integration' do
+ ENV['AJ_INTEGRATION_TESTS'] = "1"
end
-end
+ ACTIVEJOB_ADAPTERS.each do |adapter|
+ task("env:#{adapter}") { ENV['AJADAPTER'] = adapter }
-ACTIVEJOB_ADAPTERS.each do |adapter|
- namespace :test do
- Rake::TestTask.new(adapter => "#{adapter}:env") do |t|
- t.description = ""
+ Rake::TestTask.new(adapter => "test:env:#{adapter}") do |t|
+ t.description = "Run adapter tests for #{adapter}"
t.libs << 'test'
t.test_files = FileList['test/cases/**/*_test.rb']
t.verbose = true
end
namespace :isolated do
- task adapter => "#{adapter}:env" do
+ task adapter => "test:env:#{adapter}" do
+ dir = File.dirname(__FILE__)
Dir.glob("#{dir}/test/cases/**/*_test.rb").all? do |file|
sh(Gem.ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file)
end or raise 'Failures'
end
end
- end
-
- namespace adapter do
- task test: "test_#{adapter}"
- task isolated_test: "isolated_test_#{adapter}"
- task(:env) { ENV['AJADAPTER'] = adapter }
+ namespace :integration do
+ Rake::TestTask.new(adapter => ["test:env:#{adapter}", 'test:env:integration']) do |t|
+ t.description = "Run integration tests for #{adapter}"
+ t.libs << 'test'
+ t.test_files = FileList['test/integration/**/*_test.rb']
+ t.verbose = true
+ end
+ end
end
+end
+def run_without_aborting(tasks)
+ errors = []
- desc "Run #{adapter} tests"
- task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"]
+ tasks.each do |task|
+ begin
+ Rake::Task[task].invoke
+ rescue Exception
+ errors << task
+ end
+ end
- desc "Run #{adapter} tests in isolation"
- task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"]
+ abort "Errors running #{errors.join(', ')}" if errors.any?
end
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index 9944b2a1bd..a9be2a8f00 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -19,5 +19,5 @@ Gem::Specification.new do |s|
s.require_path = 'lib'
s.add_dependency 'activesupport', version
- s.add_dependency 'globalid', '>= 0.2.3'
+ s.add_dependency 'globalid', '>= 0.3.0'
end
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 93d9d1b2d8..1b582f5877 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -31,6 +31,7 @@ module ActiveJob
autoload :Base
autoload :QueueAdapters
+ autoload :ConfiguredJob
autoload :TestCase
autoload :TestHelper
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 9d4490b0fc..86b85ebdba 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -6,7 +6,7 @@ module ActiveJob
attr_reader :original_exception
def initialize(e) #:nodoc:
- super ("Error while trying to deserialize arguments: #{e.message}")
+ super("Error while trying to deserialize arguments: #{e.message}")
@original_exception = e
set_backtrace e.backtrace
end
@@ -35,16 +35,21 @@ module ActiveJob
end
private
+ GLOBALID_KEY = '_aj_globalid'.freeze
+ private_constant :GLOBALID_KEY
+
def serialize_argument(argument)
case argument
- when GlobalID::Identification
- argument.global_id.to_s
when *TYPE_WHITELIST
argument
+ when GlobalID::Identification
+ { GLOBALID_KEY => argument.to_global_id.to_s }
when Array
argument.map { |arg| serialize_argument(arg) }
when Hash
- Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
+ argument.each_with_object({}) do |(key, value), hash|
+ hash[serialize_hash_key(key)] = serialize_argument(value)
+ end
else
raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
end
@@ -52,21 +57,48 @@ module ActiveJob
def deserialize_argument(argument)
case argument
+ when String
+ GlobalID::Locator.locate(argument) || argument
+ when *TYPE_WHITELIST
+ argument
when Array
argument.map { |arg| deserialize_argument(arg) }
when Hash
- Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
+ if serialized_global_id?(argument)
+ deserialize_global_id argument
+ else
+ deserialize_hash argument
+ end
else
- GlobalID::Locator.locate(argument) || argument
+ raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
end
end
+ def serialized_global_id?(hash)
+ hash.size == 1 and hash.include?(GLOBALID_KEY)
+ end
+
+ def deserialize_global_id(hash)
+ GlobalID::Locator.locate hash[GLOBALID_KEY]
+ end
+
+ def deserialize_hash(serialized_hash)
+ serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash|
+ hash[key] = deserialize_argument(value)
+ end
+ end
+
+ RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym]
+ private_constant :RESERVED_KEYS
+
def serialize_hash_key(key)
case key
+ when *RESERVED_KEYS
+ raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")
when String, Symbol
key.to_s
else
- raise SerializationError.new("Unsupported hash key type: #{key.class.name}")
+ raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
end
end
end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 1b54786303..a3bec1f827 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -1,19 +1,19 @@
+require 'active_job/core'
require 'active_job/queue_adapter'
require 'active_job/queue_name'
require 'active_job/enqueuing'
require 'active_job/execution'
require 'active_job/callbacks'
-require 'active_job/identifier'
require 'active_job/logging'
module ActiveJob
class Base
+ include Core
include QueueAdapter
include QueueName
include Enqueuing
include Execution
include Callbacks
- include Identifier
include Logging
ActiveSupport.run_load_hooks(:active_job, self)
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index cafa3438c0..29e2a878b4 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -36,7 +36,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def before_perform(*filters, &blk)
set_callback(:perform, :before, *filters, &blk)
@@ -55,7 +55,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def after_perform(*filters, &blk)
set_callback(:perform, :after, *filters, &blk)
@@ -75,7 +75,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def around_perform(*filters, &blk)
set_callback(:perform, :around, *filters, &blk)
@@ -94,7 +94,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def before_enqueue(*filters, &blk)
set_callback(:enqueue, :before, *filters, &blk)
@@ -113,7 +113,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def after_enqueue(*filters, &blk)
set_callback(:enqueue, :after, *filters, &blk)
@@ -134,7 +134,7 @@ module ActiveJob
# def perform(video_id)
# Video.find(video_id).process
# end
- # end
+ # end
#
def around_enqueue(*filters, &blk)
set_callback(:enqueue, :around, *filters, &blk)
diff --git a/activejob/lib/active_job/configured_job.rb b/activejob/lib/active_job/configured_job.rb
new file mode 100644
index 0000000000..979280b910
--- /dev/null
+++ b/activejob/lib/active_job/configured_job.rb
@@ -0,0 +1,16 @@
+module ActiveJob
+ class ConfiguredJob #:nodoc:
+ def initialize(job_class, options={})
+ @options = options
+ @job_class = job_class
+ end
+
+ def perform_now(*args)
+ @job_class.new(*args).perform_now
+ end
+
+ def perform_later(*args)
+ @job_class.new(*args).enqueue @options
+ end
+ end
+end
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
new file mode 100644
index 0000000000..f55db5a588
--- /dev/null
+++ b/activejob/lib/active_job/core.rb
@@ -0,0 +1,89 @@
+module ActiveJob
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
+ # Job arguments
+ attr_accessor :arguments
+ attr_writer :serialized_arguments
+
+ # Timestamp when the job should be performed
+ attr_accessor :scheduled_at
+
+ # Job Identifier
+ attr_accessor :job_id
+
+ # Queue in which the job will reside.
+ attr_writer :queue_name
+ end
+
+ module ClassMethods
+ # Creates a new job instance from a hash created with +serialize+
+ def deserialize(job_data)
+ job = job_data['job_class'].constantize.new
+ job.job_id = job_data['job_id']
+ job.queue_name = job_data['queue_name']
+ job.serialized_arguments = job_data['arguments']
+ job
+ end
+
+ # Creates a job preconfigured with the given options. You can call
+ # perform_later with the job arguments to enqueue the job with the
+ # preconfigured options
+ #
+ # ==== Options
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
+ #
+ # ==== Examples
+ #
+ # VideoJob.set(queue: :some_queue).perform_later(Video.last)
+ # VideoJob.set(wait: 5.minutes).perform_later(Video.last)
+ # VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last)
+ # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
+ # VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last)
+ def set(options={})
+ ConfiguredJob.new(self, options)
+ end
+ end
+
+ # Creates a new job instance. Takes as arguments the arguments that
+ # will be passed to the perform method.
+ def initialize(*arguments)
+ @arguments = arguments
+ @job_id = SecureRandom.uuid
+ @queue_name = self.class.queue_name
+ end
+
+ # Returns a hash with the job data that can safely be passed to the
+ # queueing adapter.
+ def serialize
+ {
+ 'job_class' => self.class.name,
+ 'job_id' => job_id,
+ 'queue_name' => queue_name,
+ 'arguments' => serialize_arguments(arguments)
+ }
+ end
+
+ private
+ def deserialize_arguments_if_needed
+ if defined?(@serialized_arguments) && @serialized_arguments.present?
+ @arguments = deserialize_arguments(@serialized_arguments)
+ @serialized_arguments = nil
+ end
+ end
+
+ def serialize_arguments(serialized_args)
+ Arguments.serialize(serialized_args)
+ end
+
+ def deserialize_arguments(serialized_args)
+ Arguments.deserialize(serialized_args)
+ end
+ end
+end
+
+
+
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 3d00d51867..74bcc1fa5d 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -5,67 +5,71 @@ module ActiveJob
extend ActiveSupport::Concern
module ClassMethods
- # Push a job onto the queue. The arguments must be legal JSON types
+ # Push a job onto the queue. The arguments must be legal JSON types
# (string, int, float, nil, true, false, hash or array) or
- # GlobalID::Identification instances. Arbitrary Ruby objects
+ # GlobalID::Identification instances. Arbitrary Ruby objects
# are not supported.
#
# Returns an instance of the job class queued with args available in
# Job#arguments.
- def enqueue(*args)
- new(args).tap do |job|
- job.run_callbacks :enqueue do
- queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
- end
- end
- end
-
- # Enqueue a job to be performed at +interval+ from now.
- #
- # enqueue_in(1.week, "mike")
- #
- # Returns an instance of the job class queued with args available in
- # Job#arguments and the timestamp in Job#enqueue_at.
- def enqueue_in(interval, *args)
- enqueue_at interval.seconds.from_now, *args
+ def perform_later(*args)
+ job_or_instantiate(*args).enqueue
end
- # Enqueue a job to be performed at an explicit point in time.
- #
- # enqueue_at(Date.tomorrow.midnight, "mike")
- #
- # Returns an instance of the job class queued with args available in
- # Job#arguments and the timestamp in Job#enqueue_at.
- def enqueue_at(timestamp, *args)
- new(args).tap do |job|
- job.enqueued_at = timestamp
-
- job.run_callbacks :enqueue do
- queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
- end
+ protected
+ def job_or_instantiate(*args)
+ args.first.is_a?(self) ? args.first : new(*args)
end
- end
- end
-
- included do
- attr_accessor :arguments
- attr_accessor :enqueued_at
- end
-
- def initialize(arguments = nil)
- @arguments = arguments
end
- def retry_now
- self.class.enqueue(*arguments)
+ # Reschedule the job to be re-executed. This is useful in combination
+ # with the +rescue_from+ option. When you rescue an exception from your job
+ # you can ask Active Job to retry performing your job.
+ #
+ # ==== Options
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
+ #
+ # ==== Examples
+ #
+ # class SiteScrapperJob < ActiveJob::Base
+ # rescue_from(ErrorLoadingSite) do
+ # retry_job queue: :low_priority
+ # end
+ # def perform(*args)
+ # # raise ErrorLoadingSite if cannot scrape
+ # end
+ # end
+ def retry_job(options={})
+ enqueue options
end
- def retry_in(interval)
- self.class.enqueue_in interval, *arguments
- end
-
- def retry_at(timestamp)
- self.class.enqueue_at timestamp, *arguments
+ # Enqueues the job to be performed by the queue adapter.
+ #
+ # ==== Options
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
+ #
+ # ==== Examples
+ #
+ # my_job_instance.enqueue
+ # my_job_instance.enqueue wait: 5.minutes
+ # my_job_instance.enqueue queue: :important
+ # my_job_instance.enqueue wait_until: Date.tomorrow.midnight
+ def enqueue(options={})
+ self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
+ self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
+ self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
+ run_callbacks :enqueue do
+ if self.scheduled_at
+ self.class.queue_adapter.enqueue_at self, self.scheduled_at
+ else
+ self.class.queue_adapter.enqueue self
+ end
+ end
+ self
end
end
end
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 0e7b5bdd72..7ff857206d 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -4,15 +4,29 @@ require 'active_job/arguments'
module ActiveJob
module Execution
extend ActiveSupport::Concern
+ include ActiveSupport::Rescuable
- included do
- include ActiveSupport::Rescuable
- end
+ module ClassMethods
+ # Performs the job immediately.
+ #
+ # MyJob.perform_now("mike")
+ #
+ def perform_now(*args)
+ job_or_instantiate(*args).perform_now
+ end
- def execute(job_id, *serialized_args)
- self.job_id = job_id
- self.arguments = deserialize_arguments(serialized_args)
+ def execute(job_data) #:nodoc:
+ job = deserialize(job_data)
+ job.perform_now
+ end
+ end
+ # Performs the job immediately. The job is not sent to the queueing adapter
+ # but directly executed by blocking the execution of others until it's finished.
+ #
+ # MyJob.new(*args).perform_now
+ def perform_now
+ deserialize_arguments_if_needed
run_callbacks :perform do
perform(*arguments)
end
@@ -23,11 +37,5 @@ module ActiveJob
def perform(*)
fail NotImplementedError
end
-
- private
- def deserialize_arguments(serialized_args)
- Arguments.deserialize(serialized_args)
- end
-
end
end
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 1f25d48326..f80e6563b8 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveJob
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/lib/active_job/identifier.rb b/activejob/lib/active_job/identifier.rb
deleted file mode 100644
index c7f522087d..0000000000
--- a/activejob/lib/active_job/identifier.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'active_job/arguments'
-
-module ActiveJob
- module Identifier
- extend ActiveSupport::Concern
-
- included do
- attr_writer :job_id
- end
-
- def job_id
- @job_id ||= SecureRandom.uuid
- end
- end
-end
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index ae098a80f3..bb96668cfb 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -17,7 +17,7 @@ module ActiveJob
around_perform do |job, block, _|
tag_logger(job.class.name, job.job_id) do
- payload = {adapter: job.class.queue_adapter, job: job.class, args: job.arguments}
+ payload = {adapter: job.class.queue_adapter, job: job}
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
block.call
@@ -26,12 +26,12 @@ module ActiveJob
end
before_enqueue do |job|
- if job.enqueued_at
+ if job.scheduled_at
ActiveSupport::Notifications.instrument "enqueue_at.active_job",
- adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments, timestamp: job.enqueued_at
+ adapter: job.class.queue_adapter, job: job
else
ActiveSupport::Notifications.instrument "enqueue.active_job",
- adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments
+ adapter: job.class.queue_adapter, job: job
end
end
end
@@ -50,21 +50,33 @@ module ActiveJob
logger.formatter.current_tags.include?("ActiveJob")
end
- class LogSubscriber < ActiveSupport::LogSubscriber
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
def enqueue(event)
- info { "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event) }
+ info do
+ job = event.payload[:job]
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
+ end
end
def enqueue_at(event)
- info { "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event) }
+ info do
+ job = event.payload[:job]
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
+ end
end
def perform_start(event)
- info { "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event) }
+ info do
+ job = event.payload[:job]
+ "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
+ end
end
def perform(event)
- info { "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms" }
+ info do
+ job = event.payload[:job]
+ "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
+ end
end
private
@@ -72,12 +84,12 @@ module ActiveJob
event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
end
- def args_info(event)
- event.payload[:args].any? ? " with arguments: #{event.payload[:args].map(&:inspect).join(", ")}" : ""
+ def args_info(job)
+ job.arguments.any? ? " with arguments: #{job.arguments.map(&:inspect).join(", ")}" : ""
end
- def enqueued_at(event)
- Time.at(event.payload[:timestamp]).utc
+ def scheduled_at(event)
+ Time.at(event.payload[:job].scheduled_at).utc
end
def logger
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 345b01ef00..e865c901ce 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -1,4 +1,38 @@
module ActiveJob
+ # == Active Job adapters
+ #
+ # Active Job has adapters for the following queueing backends:
+ #
+ # * {Backburner}[https://github.com/nesquena/backburner]
+ # * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
+ # * {Qu}[https://github.com/bkeepers/qu]
+ # * {Que}[https://github.com/chanks/que]
+ # * {QueueClassic 2.x}[https://github.com/ryandotsmith/queue_classic/tree/v2.2.3]
+ # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable]
+ # * {Sidekiq}[http://sidekiq.org]
+ # * {Sneakers}[https://github.com/jondot/sneakers]
+ # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
+ #
+ # #### Backends Features
+ #
+ # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
+ # |-------------------|-------|--------|-----------|------------|---------|---------|
+ # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
+ # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
+ # | Que | Yes | Yes | Yes | Job | No | Job |
+ # | Queue Classic | Yes | Yes | No* | No | No | No |
+ # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
+ # | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
+ # | Sneakers | Yes | Yes | No | Queue | Queue | No |
+ # | Sucker Punch | Yes | Yes | No | No | No | No |
+ # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
+ # | Active Job | Yes | Yes | Yes | No | No | No |
+ #
+ # NOTE:
+ # Queue Classic does not support Job scheduling. However you can implement this
+ # yourself or you can use the queue_classic-later gem. See the documentation for
+ # ActiveJob::QueueAdapters::QueueClassicAdapter.
+ #
module QueueAdapters
extend ActiveSupport::Autoload
diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
index 8ebee36e45..2453d065de 100644
--- a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
@@ -2,22 +2,32 @@ require 'backburner'
module ActiveJob
module QueueAdapters
+ # == Backburner adapter for Active Job
+ #
+ # Backburner is a beanstalkd-powered job queue that can handle a very
+ # high volume of jobs. You create background jobs and place them on
+ # multiple work queues to be processed later. Read more about
+ # Backburner {here}[https://github.com/nesquena/backburner].
+ #
+ # To use Backburner set the queue_adapter config to +:backburner+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :backburner
class BackburnerAdapter
class << self
- def enqueue(job, *args)
- Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name
+ def enqueue(job) #:nodoc:
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
delay = timestamp - Time.current.to_f
- Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name, delay: delay
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
class << self
- def perform(job_name, *args)
- job_name.constantize.new.execute(*args)
+ def perform(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
index a00569833a..4d27c4fff8 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -2,20 +2,36 @@ require 'delayed_job'
module ActiveJob
module QueueAdapters
+ # == Delayed Job adapter for Active Job
+ #
+ # Delayed::Job (or DJ) encapsulates the common pattern of asynchronously
+ # executing longer tasks in the background. Although DJ can have many
+ # storage backends one of the most used is based on Active Record.
+ # Read more about Delayed Job {here}[https://github.com/collectiveidea/delayed_job].
+ #
+ # To use Delayed Job set the queue_adapter config to +:delayed_job+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :delayed_job
class DelayedJobAdapter
class << self
- def enqueue(job, *args)
- JobWrapper.new.delay(queue: job.queue_name).perform(job, *args)
+ def enqueue(job) #:nodoc:
+ Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
end
- def enqueue_at(job, timestamp, *args)
- JobWrapper.new.delay(queue: job.queue_name, run_at: Time.at(timestamp)).perform(job, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
+ Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
end
end
- class JobWrapper
- def perform(job, *args)
- job.new.execute(*args)
+ class JobWrapper #:nodoc:
+ attr_accessor :job_data
+
+ def initialize(job_data)
+ @job_data = job_data
+ end
+
+ def perform
+ Base.execute(job_data)
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 5805340fb0..e498454909 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -1,12 +1,20 @@
module ActiveJob
module QueueAdapters
+ # == Active Job Inline adapter
+ #
+ # When enqueueing jobs with the Inline adapter the job will be executed
+ # immediately.
+ #
+ # To use the Inline set the queue_adapter config to +:inline+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :inline
class InlineAdapter
class << self
- def enqueue(job, *args)
- job.new.execute(*args)
+ def enqueue(job) #:nodoc:
+ Base.execute(job.serialize)
end
- def enqueue_at(*)
+ def enqueue_at(*) #:nodoc:
raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at https://github.com/rails/activejob")
end
end
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
index 5cb741c094..30aa5a4670 100644
--- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -2,27 +2,39 @@ require 'qu'
module ActiveJob
module QueueAdapters
+ # == Qu adapter for Active Job
+ #
+ # Qu is a Ruby library for queuing and processing background jobs. It is
+ # heavily inspired by delayed_job and Resque. Qu was created to overcome
+ # some shortcomings in the existing queuing libraries.
+ # The advantages of Qu are: Multiple backends (redis, mongo), jobs are
+ # requeued when worker is killed, resque-like API.
+ #
+ # Read more about Qu {here}[https://github.com/bkeepers/qu].
+ #
+ # To use Qu set the queue_adapter config to +:qu+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :qu
class QuAdapter
class << self
- def enqueue(job, *args)
- Qu::Payload.new(klass: JobWrapper, args: [job.name, *args]).tap do |payload|
+ def enqueue(job, *args) #:nodoc:
+ Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
payload.instance_variable_set(:@queue, job.queue_name)
end.push
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp, *args) #:nodoc:
raise NotImplementedError
end
end
- class JobWrapper < Qu::Job
- def initialize(job_name, *args)
- @job = job_name.constantize
- @args = args
+ class JobWrapper < Qu::Job #:nodoc:
+ def initialize(job_data)
+ @job_data = job_data
end
def perform
- @job.new.execute(*@args)
+ Base.execute @job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
index 9c84c74f83..e501fe0368 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -2,20 +2,32 @@ require 'que'
module ActiveJob
module QueueAdapters
+ # == Que adapter for Active Job
+ #
+ # Que is a high-performance alternative to DelayedJob or QueueClassic that
+ # improves the reliability of your application by protecting your jobs with
+ # the same ACID guarantees as the rest of your data. Que is a queue for
+ # Ruby and PostgreSQL that manages jobs using advisory locks.
+ #
+ # Read more about Que {here}[https://github.com/chanks/que].
+ #
+ # To use Que set the queue_adapter config to +:que+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
class << self
- def enqueue(job, *args)
- JobWrapper.enqueue job.name, *args, queue: job.queue_name
+ def enqueue(job) #:nodoc:
+ JobWrapper.enqueue job.serialize, queue: job.queue_name
end
- def enqueue_at(job, timestamp, *args)
- JobWrapper.enqueue job.name, *args, queue: job.queue_name, run_at: Time.at(timestamp)
+ def enqueue_at(job, timestamp) #:nodoc:
+ JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
end
end
- class JobWrapper < Que::Job
- def run(job_name, *args)
- job_name.constantize.new.execute(*args)
+ class JobWrapper < Que::Job #:nodoc:
+ def run(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
index 914390a958..f160932578 100644
--- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -2,25 +2,39 @@ require 'queue_classic'
module ActiveJob
module QueueAdapters
+ # == Queue Classic adapter for Active Job
+ #
+ # queue_classic provides a simple interface to a PostgreSQL-backed message
+ # queue. queue_classic specializes in concurrent locking and minimizing
+ # database load while providing a simple, intuitive developer experience.
+ # queue_classic assumes that you are already using PostgreSQL in your
+ # production environment and that adding another dependency (e.g. redis,
+ # beanstalkd, 0mq) is undesirable.
+ #
+ # Read more about Queue Classic {here}[https://github.com/ryandotsmith/queue_classic].
+ #
+ # To use Queue Classic set the queue_adapter config to +:queue_classic+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :queue_classic
class QueueClassicAdapter
class << self
- def enqueue(job, *args)
- build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
+ def enqueue(job) #:nodoc:
+ build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
queue = build_queue(job.queue_name)
unless queue.respond_to?(:enqueue_at)
raise NotImplementedError, 'To be able to schedule jobs with Queue Classic ' \
'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. '
'You can implement this yourself or you can use the queue_classic-later gem.'
end
- queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.name, *args)
+ queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
end
# Builds a <tt>QC::Queue</tt> object to schedule jobs on.
#
- # If you have a custom <tt>QC::Queue</tt> subclass you'll need to suclass
+ # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
# <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
# <tt>build_queue</tt> method.
def build_queue(queue_name)
@@ -28,10 +42,10 @@ module ActiveJob
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
class << self
- def perform(job_name, *args)
- job_name.constantize.new.execute(*args)
+ def perform(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
index da8212fc9b..88c6b48fef 100644
--- a/activejob/lib/active_job/queue_adapters/resque_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
@@ -14,25 +14,36 @@ end
module ActiveJob
module QueueAdapters
+ # == Resque adapter for Active Job
+ #
+ # Resque (pronounced like "rescue") is a Redis-backed library for creating
+ # background jobs, placing those jobs on multiple queues, and processing
+ # them later.
+ #
+ # Read more about Resque {here}[https://github.com/resque/resque].
+ #
+ # To use Resque set the queue_adapter config to +:resque+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :resque
class ResqueAdapter
class << self
- def enqueue(job, *args)
- Resque.enqueue_to job.queue_name, JobWrapper, job.name, *args
+ def enqueue(job) #:nodoc:
+ Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
unless Resque.respond_to?(:enqueue_at_with_queue)
raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
"resque-scheduler gem. Please add it to your Gemfile and run bundle install"
end
- Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.name, *args
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
class << self
- def perform(job_name, *args)
- job_name.constantize.new.execute(*args)
+ def perform(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 3e20bec44c..7d80a6fd7a 100644
--- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -2,32 +2,44 @@ require 'sidekiq'
module ActiveJob
module QueueAdapters
+ # == Sidekiq adapter for Active Job
+ #
+ # Simple, efficient background processing for Ruby. Sidekiq uses threads to
+ # handle many jobs at the same time in the same process. It does not
+ # require Rails but will integrate tightly with it to make background
+ # processing dead simple.
+ #
+ # Read more about Sidekiq {here}[http://sidekiq.org].
+ #
+ # To use Sidekiq set the queue_adapter config to +:sidekiq+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :sidekiq
class SidekiqAdapter
class << self
- def enqueue(job, *args)
+ def enqueue(job) #:nodoc:
#Sidekiq::Client does not support symbols as keys
Sidekiq::Client.push \
'class' => JobWrapper,
'queue' => job.queue_name,
- 'args' => [ job, *args ],
+ 'args' => [ job.serialize ],
'retry' => true
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
Sidekiq::Client.push \
'class' => JobWrapper,
'queue' => job.queue_name,
- 'args' => [ job, *args ],
+ 'args' => [ job.serialize ],
'retry' => true,
'at' => timestamp
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
include Sidekiq::Worker
- def perform(job_name, *args)
- job_name.constantize.new.execute(*args)
+ def perform(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
index 48b3df6a46..6d60a2f303 100644
--- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -3,29 +3,41 @@ require 'thread'
module ActiveJob
module QueueAdapters
+ # == Sneakers adapter for Active Job
+ #
+ # A high-performance RabbitMQ background processing framework for Ruby.
+ # Sneakers is being used in production for both I/O and CPU intensive
+ # workloads, and have achieved the goals of high-performance and
+ # 0-maintenance, as designed.
+ #
+ # Read more about Sneakers {here}[https://github.com/jondot/sneakers].
+ #
+ # To use Sneakers set the queue_adapter config to +:sneakers+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :sneakers
class SneakersAdapter
@monitor = Monitor.new
class << self
- def enqueue(job, *args)
+ def enqueue(job) #:nodoc:
@monitor.synchronize do
JobWrapper.from_queue job.queue_name
- JobWrapper.enqueue ActiveSupport::JSON.encode([ job.name, *args ])
+ JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize)
end
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
raise NotImplementedError
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
include Sneakers::Worker
- from_queue 'active_jobs_default'
+ from_queue 'default'
def work(msg)
- job_name, *args = ActiveSupport::JSON.decode(msg)
- job_name.constantize.new.execute(*args)
+ job_data = ActiveSupport::JSON.decode(msg)
+ Base.execute job_data
ack!
end
end
diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
index 16f05744f3..be9e7fd03a 100644
--- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -2,22 +2,37 @@ require 'sucker_punch'
module ActiveJob
module QueueAdapters
+ # == Sucker Punch adapter for Active Job
+ #
+ # Sucker Punch is a single-process Ruby asynchronous processing library.
+ # It's girl_friday and DSL sugar on top of Celluloid. With Celluloid's
+ # actor pattern, we can do asynchronous processing within a single process.
+ # This reduces costs of hosting on a service like Heroku along with the
+ # memory footprint of having to maintain additional jobs if hosting on
+ # a dedicated server. All queues can run within a single Rails/Sinatra
+ # process.
+ #
+ # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch].
+ #
+ # To use Sucker Punch set the queue_adapter config to +:sucker_punch+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :sucker_punch
class SuckerPunchAdapter
class << self
- def enqueue(job, *args)
- JobWrapper.new.async.perform job, *args
+ def enqueue(job) #:nodoc:
+ JobWrapper.new.async.perform job.serialize
end
- def enqueue_at(job, timestamp, *args)
+ def enqueue_at(job, timestamp) #:nodoc:
raise NotImplementedError
end
end
- class JobWrapper
+ class JobWrapper #:nodoc:
include SuckerPunch::Job
- def perform(job, *args)
- job.new.execute(*args)
+ def perform(job_data)
+ Base.execute job_data
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index 185d6fc7e6..e4fdf60008 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -1,76 +1,46 @@
module ActiveJob
module QueueAdapters
+ # == Test adapter for Active Job
+ #
+ # The test adapter should be used only in testing. Along with
+ # <tt>ActiveJob::TestCase</tt> and <tt>ActiveJob::TestHelper</tt>
+ # it makes a great tool to test your Rails application.
+ #
+ # To use the test adapter set queue_adapter config to +:test+.
+ #
+ # Rails.application.config.active_job.queue_adapter = :test
class TestAdapter
- attr_accessor(:perform_enqueued_jobs) { false }
- attr_accessor(:perform_enqueued_at_jobs) { false }
delegate :name, to: :class
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
+ attr_writer(:enqueued_jobs, :performed_jobs)
# Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
def enqueued_jobs
@enqueued_jobs ||= []
end
- # Allows you to overwrite the default enqueued jobs store from an array to some
- # other object. If you just want to clear the store,
- # call ActiveJob::QueueAdapters::TestAdapter.enqueued_jobs.clear.
- #
- # If you place another object here, please make sure it responds to:
- #
- # * << (message)
- # * clear
- # * length
- # * size
- # * and other common Array methods
- def enqueued_jobs=(val)
- @enqueued_jobs = val
- end
-
# Provides a store of all the performed jobs with the TestAdapter so you can check them.
def performed_jobs
@performed_jobs ||= []
end
- # Allows you to overwrite the default performed jobs store from an array to some
- # other object. If you just want to clear the store,
- # call ActiveJob::QueueAdapters::TestAdapter.performed_jobs.clear.
- #
- # If you place another object here, please make sure it responds to:
- #
- # * << (message)
- # * clear
- # * length
- # * size
- # * and other common Array methods
- def performed_jobs=(val)
- @performed_jobs = val
- end
-
- def enqueue(job, *args)
- if perform_enqueued_jobs?
- performed_jobs << {job: job, args: args, queue: job.queue_name}
- job.new.execute(*args)
+ def enqueue(job) #:nodoc:
+ if perform_enqueued_jobs
+ performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
+ job.perform_now
else
- enqueued_jobs << {job: job, args: args, queue: job.queue_name}
+ enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
end
end
- def enqueue_at(job, timestamp, *args)
- if perform_enqueued_at_jobs?
- performed_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp}
- job.new.execute(*args)
+ def enqueue_at(job, timestamp) #:nodoc:
+ if perform_enqueued_at_jobs
+ performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
+ job.perform_now
else
- enqueued_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp}
+ enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
end
end
-
- private
- def perform_enqueued_jobs?
- perform_enqueued_jobs
- end
-
- def perform_enqueued_at_jobs?
- perform_enqueued_at_jobs
- end
end
end
end
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
index 9698835b6e..45acb71605 100644
--- a/activejob/lib/active_job/queue_name.rb
+++ b/activejob/lib/active_job/queue_name.rb
@@ -6,16 +6,33 @@ module ActiveJob
mattr_accessor(:queue_name_prefix)
mattr_accessor(:default_queue_name) { "default" }
- def queue_as(part_name)
+ def queue_as(part_name=nil, &block)
+ if block_given?
+ self.queue_name = block
+ else
+ self.queue_name = queue_name_from_part(part_name)
+ end
+ end
+
+ def queue_name_from_part(part_name) #:nodoc:
queue_name = part_name.to_s.presence || default_queue_name
name_parts = [queue_name_prefix.presence, queue_name]
- self.queue_name = name_parts.compact.join('_')
+ name_parts.compact.join('_')
end
end
included do
- class_attribute :queue_name
+ class_attribute :queue_name, instance_accessor: false
self.queue_name = default_queue_name
end
+
+ # Returns the name of the queue the job will be run on
+ def queue_name
+ if @queue_name.is_a?(Proc)
+ @queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name))
+ end
+ @queue_name
+ end
+
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 767619097c..af62fae9b9 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -21,9 +21,9 @@ module ActiveJob
#
# def test_jobs
# assert_enqueued_jobs 0
- # HelloJob.enqueue('david')
+ # HelloJob.perform_later('david')
# assert_enqueued_jobs 1
- # HelloJob.enqueue('abdelkader')
+ # HelloJob.perform_later('abdelkader')
# assert_enqueued_jobs 2
# end
#
@@ -32,12 +32,12 @@ module ActiveJob
#
# def test_jobs_again
# assert_enqueued_jobs 1 do
- # HelloJob.enqueue('cristian')
+ # HelloJob.perform_later('cristian')
# end
#
# assert_enqueued_jobs 2 do
- # HelloJob.enqueue('aaron')
- # HelloJob.enqueue('rafael')
+ # HelloJob.perform_later('aaron')
+ # HelloJob.perform_later('rafael')
# end
# end
def assert_enqueued_jobs(number)
@@ -57,7 +57,7 @@ module ActiveJob
#
# def test_jobs
# assert_no_enqueued_jobs
- # HelloJob.enqueue('jeremy')
+ # HelloJob.perform_later('jeremy')
# assert_enqueued_jobs 1
# end
#
@@ -80,9 +80,9 @@ module ActiveJob
#
# def test_jobs
# assert_performed_jobs 0
- # HelloJob.enqueue('xavier')
+ # HelloJob.perform_later('xavier')
# assert_performed_jobs 1
- # HelloJob.enqueue('yves')
+ # HelloJob.perform_later('yves')
# assert_performed_jobs 2
# end
#
@@ -91,12 +91,12 @@ module ActiveJob
#
# def test_jobs_again
# assert_performed_jobs 1 do
- # HelloJob.enqueue('robin')
+ # HelloJob.perform_later('robin')
# end
#
# assert_performed_jobs 2 do
- # HelloJob.enqueue('carlos')
- # HelloJob.enqueue('sean')
+ # HelloJob.perform_later('carlos')
+ # HelloJob.perform_later('sean')
# end
# end
def assert_performed_jobs(number)
@@ -116,7 +116,7 @@ module ActiveJob
#
# def test_jobs
# assert_no_performed_jobs
- # HelloJob.enqueue('matthew')
+ # HelloJob.perform_later('matthew')
# assert_performed_jobs 1
# end
#
@@ -139,7 +139,7 @@ module ActiveJob
#
# def assert_enqueued_job
# assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
- # MyJob.enqueue(1,2,3)
+ # MyJob.perform_later(1,2,3)
# end
# end
def assert_enqueued_with(args = {}, &_block)
@@ -150,7 +150,7 @@ module ActiveJob
matching_job = enqueued_jobs.any? do |job|
args.all? { |key, value| value == job[key] }
end
- assert matching_job
+ assert matching_job, "No enqueued job found with #{args}"
ensure
queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs
end
@@ -159,7 +159,7 @@ module ActiveJob
#
# def test_assert_performed_with
# assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
- # MyJob.enqueue(1,2,3)
+ # MyJob.perform_later(1,2,3)
# end
# end
def assert_performed_with(args = {}, &_block)
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
new file mode 100644
index 0000000000..dbe36fc572
--- /dev/null
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -0,0 +1,84 @@
+require 'helper'
+require 'active_job/arguments'
+require 'models/person'
+require 'active_support/core_ext/hash/indifferent_access'
+
+class ArgumentSerializationTest < ActiveSupport::TestCase
+ setup do
+ @person = Person.find('5')
+ end
+
+ [ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
+ 'a', true, false,
+ [ 1, 'a' ],
+ { 'a' => 1 }
+ ].each do |arg|
+ test "serializes #{arg.class} verbatim" do
+ assert_arguments_unchanged arg
+ end
+ end
+
+ [ :a, Object.new, self, Person.find('5').to_gid ].each do |arg|
+ test "does not serialize #{arg.class}" do
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [ arg ]
+ end
+
+ assert_raises ActiveJob::DeserializationError do
+ ActiveJob::Arguments.deserialize [ arg ]
+ end
+ end
+ end
+
+ test 'should convert records to Global IDs' do
+ assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s]
+ end
+
+ test 'should dive deep into arrays and hashes' do
+ assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]]
+ assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access]
+ end
+
+ test 'should stringify symbol hash keys' do
+ assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ])
+ end
+
+ test 'should disallow non-string/symbol hash keys' do
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [ { 1 => 2 } ]
+ end
+
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [ { :a => [{ 2 => 3 }] } ]
+ end
+
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [ '_aj_globalid' => 1 ]
+ end
+
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [ :_aj_globalid => 1 ]
+ end
+ end
+
+ test 'should not allow non-primitive objects' do
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [Object.new]
+ end
+
+ assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [1, [Object.new]]
+ end
+ end
+
+ private
+ def assert_arguments_unchanged(*args)
+ assert_arguments_roundtrip args, args
+ end
+
+ def assert_arguments_roundtrip(args, expected_serialized_args)
+ serialized = ActiveJob::Arguments.serialize(args)
+ assert_equal expected_serialized_args, serialized
+ assert_equal args, ActiveJob::Arguments.deserialize(serialized)
+ end
+end
diff --git a/activejob/test/cases/callbacks_test.rb b/activejob/test/cases/callbacks_test.rb
index 9a0657ee89..9af2380767 100644
--- a/activejob/test/cases/callbacks_test.rb
+++ b/activejob/test/cases/callbacks_test.rb
@@ -5,7 +5,8 @@ require 'active_support/core_ext/object/inclusion'
class CallbacksTest < ActiveSupport::TestCase
test 'perform callbacks' do
- performed_callback_job = CallbackJob.new.tap { |j| j.execute("A-JOB-ID") }
+ performed_callback_job = CallbackJob.new("A-JOB-ID")
+ performed_callback_job.perform_now
assert "CallbackJob ran before_perform".in? performed_callback_job.history
assert "CallbackJob ran after_perform".in? performed_callback_job.history
assert "CallbackJob ran around_perform_start".in? performed_callback_job.history
@@ -13,7 +14,7 @@ class CallbacksTest < ActiveSupport::TestCase
end
test 'enqueue callbacks' do
- enqueued_callback_job = CallbackJob.enqueue
+ enqueued_callback_job = CallbackJob.perform_later
assert "CallbackJob ran before_enqueue".in? enqueued_callback_job.history
assert "CallbackJob ran after_enqueue".in? enqueued_callback_job.history
assert "CallbackJob ran around_enqueue_start".in? enqueued_callback_job.history
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index fc1b77744c..db22783030 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -9,7 +9,7 @@ class JobSerializationTest < ActiveSupport::TestCase
end
test 'serialize job with gid' do
- GidJob.enqueue @person
+ GidJob.perform_later @person
assert_equal "Person with ID: 5", JobBuffer.last_value
end
end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index 888c183a0b..9c56ee08b6 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -42,34 +42,43 @@ class AdapterTest < ActiveSupport::TestCase
def test_uses_active_job_as_tag
- HelloJob.enqueue "Cristian"
+ HelloJob.perform_later "Cristian"
assert_match(/\[ActiveJob\]/, @logger.messages)
end
+ def test_uses_job_name_as_tag
+ LoggingJob.perform_later "Dummy"
+ assert_match(/\[LoggingJob\]/, @logger.messages)
+ end
+
+ def test_uses_job_id_as_tag
+ LoggingJob.perform_later "Dummy"
+ assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages)
+ end
+
+ def test_logs_correct_queue_name
+ original_queue_name = LoggingJob.queue_name
+ LoggingJob.queue_as :php_jobs
+ LoggingJob.perform_later("Dummy")
+ assert_match(/to .*?\(php_jobs\).*/, @logger.messages)
+ ensure
+ LoggingJob.queue_name = original_queue_name
+ end
+
def test_enqueue_job_logging
- HelloJob.enqueue "Cristian"
+ HelloJob.perform_later "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
end
def test_perform_job_logging
- LoggingJob.enqueue "Dummy"
+ LoggingJob.perform_later "Dummy"
assert_match(/Performing LoggingJob from .*? with arguments:.*Dummy/, @logger.messages)
assert_match(/Dummy, here is it: Dummy/, @logger.messages)
assert_match(/Performed LoggingJob from .*? in .*ms/, @logger.messages)
end
- def test_perform_uses_job_name_job_logging
- LoggingJob.enqueue "Dummy"
- assert_match(/\[LoggingJob\]/, @logger.messages)
- end
-
- def test_perform_uses_job_id_job_logging
- LoggingJob.enqueue "Dummy"
- assert_match(/\[LOGGING-JOB-ID\]/, @logger.messages)
- end
-
def test_perform_nested_jobs_logging
- NestedJob.enqueue
+ NestedJob.perform_later
assert_match(/\[LoggingJob\] \[.*?\]/, @logger.messages)
assert_match(/\[ActiveJob\] Enqueued NestedJob \(Job ID: .*\) to/, @logger.messages)
assert_match(/\[ActiveJob\] \[NestedJob\] \[NESTED-JOB-ID\] Performing NestedJob from/, @logger.messages)
@@ -81,14 +90,14 @@ class AdapterTest < ActiveSupport::TestCase
end
def test_enqueue_at_job_logging
- HelloJob.enqueue_at 1, "Cristian"
+ HelloJob.set(wait_until: 24.hours.from_now).perform_later "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
rescue NotImplementedError
skip
end
def test_enqueue_in_job_logging
- HelloJob.enqueue_in 2, "Cristian"
+ HelloJob.set(wait: 2.seconds).perform_later "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
rescue NotImplementedError
skip
diff --git a/activejob/test/cases/parameters_test.rb b/activejob/test/cases/parameters_test.rb
deleted file mode 100644
index 78853c51e1..0000000000
--- a/activejob/test/cases/parameters_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'helper'
-require 'active_job/arguments'
-require 'models/person'
-require 'active_support/core_ext/hash/indifferent_access'
-
-class ParameterSerializationTest < ActiveSupport::TestCase
- test 'should make no change to regular values' do
- assert_equal [ 1, "something" ], ActiveJob::Arguments.serialize([ 1, "something" ])
- end
-
- test 'should not allow complex objects' do
- assert_equal [ nil ], ActiveJob::Arguments.serialize([ nil ])
- assert_equal [ 1 ], ActiveJob::Arguments.serialize([ 1 ])
- assert_equal [ 1.0 ], ActiveJob::Arguments.serialize([ 1.0 ])
- assert_equal [ 'a' ], ActiveJob::Arguments.serialize([ 'a' ])
- assert_equal [ true ], ActiveJob::Arguments.serialize([ true ])
- assert_equal [ false ], ActiveJob::Arguments.serialize([ false ])
- assert_equal [ { "a" => 1, "b" => 2 } ], ActiveJob::Arguments.serialize([ { a: 1, "b" => 2 } ])
- assert_equal [ [ 1 ] ], ActiveJob::Arguments.serialize([ [ 1 ] ])
- assert_equal [ 1_000_000_000_000_000_000_000 ], ActiveJob::Arguments.serialize([ 1_000_000_000_000_000_000_000 ])
-
- err = assert_raises ActiveJob::SerializationError do
- ActiveJob::Arguments.serialize([ 1, self ])
- end
- assert_equal "Unsupported argument type: #{self.class.name}", err.message
- end
-
- test 'should dive deep into arrays or hashes' do
- assert_equal [ { "a" => Person.find(5).gid.to_s }.with_indifferent_access ], ActiveJob::Arguments.serialize([ { a: Person.find(5) } ])
- assert_equal [ [ Person.find(5).gid.to_s ] ], ActiveJob::Arguments.serialize([ [ Person.find(5) ] ])
- end
-
- test 'should dive deep into arrays or hashes and raise exception on complex objects' do
- err = assert_raises ActiveJob::SerializationError do
- ActiveJob::Arguments.serialize([ 1, [self] ])
- end
- assert_equal "Unsupported argument type: #{self.class.name}", err.message
- end
-
- test 'shoud dive deep into hashes and allow raise exception on not string/symbol keys' do
- err = assert_raises ActiveJob::SerializationError do
- ActiveJob::Arguments.serialize([ [ { 1 => 2 } ] ])
- end
- assert_equal "Unsupported hash key type: Fixnum", err.message
- end
-
- test 'should serialize records with global id' do
- assert_equal [ Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ Person.find(5) ])
- end
-
- test 'should serialize values and records together' do
- assert_equal [ 3, Person.find(5).gid.to_s ], ActiveJob::Arguments.serialize([ 3, Person.find(5) ])
- end
-end
-
-class ParameterDeserializationTest < ActiveSupport::TestCase
- test 'should make no change to regular values' do
- assert_equal [ 1, "something" ], ActiveJob::Arguments.deserialize([ 1, "something" ])
- end
-
- test 'should deserialize records with global id' do
- assert_equal [ Person.find(5) ], ActiveJob::Arguments.deserialize([ Person.find(5).gid ])
- end
-
- test 'should serialize values and records together' do
- assert_equal [ 3, Person.find(5) ], ActiveJob::Arguments.deserialize([ 3, Person.find(5).gid ])
- end
-
- test 'should dive deep when deserialising arrays' do
- assert_equal [ [ 3, Person.find(5) ] ], ActiveJob::Arguments.deserialize([ [ 3, Person.find(5).gid ] ])
- end
-
- test 'should dive deep when deserialising hashes' do
- assert_equal [ { "5" => Person.find(5) } ], ActiveJob::Arguments.deserialize([ { "5" => Person.find(5).gid } ])
- end
-
-end
diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb
index fdfd1afceb..4052477543 100644
--- a/activejob/test/cases/queue_naming_test.rb
+++ b/activejob/test/cases/queue_naming_test.rb
@@ -8,20 +8,38 @@ class QueueNamingTest < ActiveSupport::TestCase
assert_equal "default", HelloJob.queue_name
end
- test 'name appended in job' do
+ test 'uses given queue name job' do
begin
+ original_queue_name = HelloJob.queue_name
HelloJob.queue_as :greetings
- LoggingJob.queue_as :bookkeeping
+ assert_equal "greetings", HelloJob.new.queue_name
+ ensure
+ HelloJob.queue_name = original_queue_name
+ end
+ end
- assert_equal "default", NestedJob.queue_name
- assert_equal "greetings", HelloJob.queue_name
- assert_equal "bookkeeping", LoggingJob.queue_name
+ test 'evals block given to queue_as to determine queue' do
+ begin
+ original_queue_name = HelloJob.queue_name
+ HelloJob.queue_as { :another }
+ assert_equal "another", HelloJob.new.queue_name
ensure
- HelloJob.queue_name = LoggingJob.queue_name = ActiveJob::Base.default_queue_name
+ HelloJob.queue_name = original_queue_name
end
end
- test 'should prefix the queue name' do
+ test 'can use arguments to determine queue_name in queue_as block' do
+ begin
+ original_queue_name = HelloJob.queue_name
+ HelloJob.queue_as { self.arguments.first=='1' ? :one : :two }
+ assert_equal "one", HelloJob.new('1').queue_name
+ assert_equal "two", HelloJob.new('3').queue_name
+ ensure
+ HelloJob.queue_name = original_queue_name
+ end
+ end
+
+ test 'queu_name_prefix prepended to the queue name' do
begin
original_queue_name_prefix = ActiveJob::Base.queue_name_prefix
original_queue_name = HelloJob.queue_name
@@ -35,4 +53,9 @@ class QueueNamingTest < ActiveSupport::TestCase
end
end
+ test 'uses queue passed to #set' do
+ job = HelloJob.set(queue: :some_queue).perform_later
+ assert_equal "some_queue", job.queue_name
+ end
+
end
diff --git a/activejob/test/cases/queuing_test.rb b/activejob/test/cases/queuing_test.rb
index f020316d7e..0eeabbf693 100644
--- a/activejob/test/cases/queuing_test.rb
+++ b/activejob/test/cases/queuing_test.rb
@@ -9,18 +9,18 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'run queued job' do
- HelloJob.enqueue
+ HelloJob.perform_later
assert_equal "David says hello", JobBuffer.last_value
end
test 'run queued job with arguments' do
- HelloJob.enqueue "Jamie"
+ HelloJob.perform_later "Jamie"
assert_equal "Jamie says hello", JobBuffer.last_value
end
test 'run queued job later' do
begin
- result = HelloJob.enqueue_at 1.second.ago, "Jamie"
+ result = HelloJob.set(wait_until: 1.second.ago).perform_later "Jamie"
assert result
rescue NotImplementedError
skip
@@ -28,15 +28,15 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'job returned by enqueue has the arguments available' do
- job = HelloJob.enqueue "Jamie"
+ job = HelloJob.perform_later "Jamie"
assert_equal [ "Jamie" ], job.arguments
end
- test 'job returned by enqueue_at has the timestamp available' do
+ test 'job returned by perform_at has the timestamp available' do
begin
- job = HelloJob.enqueue_at Time.utc(2014, 1, 1)
- assert_equal Time.utc(2014, 1, 1), job.enqueued_at
+ job = HelloJob.set(wait_until: Time.utc(2014, 1, 1)).perform_later
+ assert_equal Time.utc(2014, 1, 1).to_f, job.scheduled_at
rescue NotImplementedError
skip
end
diff --git a/activejob/test/cases/rescue_test.rb b/activejob/test/cases/rescue_test.rb
index 3af147383e..1b6c2e9fac 100644
--- a/activejob/test/cases/rescue_test.rb
+++ b/activejob/test/cases/rescue_test.rb
@@ -10,27 +10,27 @@ class RescueTest < ActiveSupport::TestCase
end
test 'rescue perform exception with retry' do
- job = RescueJob.new
- job.execute(SecureRandom.uuid, "david")
+ job = RescueJob.new("david")
+ job.perform_now
assert_equal [ "rescued from ArgumentError", "performed beautifully" ], JobBuffer.values
end
test 'let through unhandled perform exception' do
- job = RescueJob.new
+ job = RescueJob.new("other")
assert_raises(RescueJob::OtherError) do
- job.execute(SecureRandom.uuid, "other")
+ job.perform_now
end
end
test 'rescue from deserialization errors' do
- RescueJob.enqueue Person.new(404)
+ RescueJob.perform_later Person.new(404)
assert_includes JobBuffer.values, 'rescued from DeserializationError'
assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound'
assert_not_includes JobBuffer.values, 'performed beautifully'
end
test "should not wrap DeserializationError in DeserializationError" do
- RescueJob.enqueue [Person.new(404)]
+ RescueJob.perform_later [Person.new(404)]
assert_includes JobBuffer.values, 'DeserializationError original exception was Person::RecordNotFound'
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 240aa23ce3..71c505a65f 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -11,7 +11,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_jobs
assert_nothing_raised do
assert_enqueued_jobs 1 do
- HelloJob.enqueue('david')
+ HelloJob.perform_later('david')
end
end
end
@@ -19,27 +19,27 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_repeated_enqueued_jobs_calls
assert_nothing_raised do
assert_enqueued_jobs 1 do
- HelloJob.enqueue('abdelkader')
+ HelloJob.perform_later('abdelkader')
end
end
assert_nothing_raised do
assert_enqueued_jobs 2 do
- HelloJob.enqueue('sean')
- HelloJob.enqueue('yves')
+ HelloJob.perform_later('sean')
+ HelloJob.perform_later('yves')
end
end
end
def test_assert_enqueued_jobs_with_no_block
assert_nothing_raised do
- HelloJob.enqueue('rafael')
+ HelloJob.perform_later('rafael')
assert_enqueued_jobs 1
end
assert_nothing_raised do
- HelloJob.enqueue('aaron')
- HelloJob.enqueue('matthew')
+ HelloJob.perform_later('aaron')
+ HelloJob.perform_later('matthew')
assert_enqueued_jobs 3
end
end
@@ -48,7 +48,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase
assert_nothing_raised do
assert_no_enqueued_jobs do
# Scheduled jobs are being performed in this context
- HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey')
+ HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
end
end
end
@@ -56,7 +56,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_jobs_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_jobs 2 do
- HelloJob.enqueue('xavier')
+ HelloJob.perform_later('xavier')
end
end
@@ -66,8 +66,8 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_jobs_too_many_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_jobs 1 do
- HelloJob.enqueue('cristian')
- HelloJob.enqueue('guillermo')
+ HelloJob.perform_later('cristian')
+ HelloJob.perform_later('guillermo')
end
end
@@ -77,7 +77,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_no_enqueued_jobs_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_enqueued_jobs do
- HelloJob.enqueue('jeremy')
+ HelloJob.perform_later('jeremy')
end
end
@@ -86,28 +86,30 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_job
assert_enqueued_with(job: LoggingJob, queue: 'default') do
- NestedJob.enqueue_at(Date.tomorrow.noon)
+ NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
end
end
def test_assert_enqueued_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_with(job: LoggingJob, queue: 'default') do
- NestedJob.enqueue
+ NestedJob.perform_later
end
end
- assert_raise ActiveSupport::TestCase::Assertion do
+ error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_with(job: NestedJob, queue: 'low') do
- NestedJob.enqueue
+ NestedJob.perform_later
end
end
+
+ assert_equal 'No enqueued job found with {:job=>NestedJob, :queue=>"low"}', error.message
end
def test_assert_enqueued_job_args
assert_raise ArgumentError do
assert_enqueued_with(class: LoggingJob) do
- NestedJob.enqueue_at(Date.tomorrow.noon)
+ NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
end
end
end
@@ -119,7 +121,7 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_jobs
assert_nothing_raised do
assert_performed_jobs 1 do
- HelloJob.enqueue('david')
+ HelloJob.perform_later('david')
end
end
end
@@ -127,27 +129,27 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_repeated_performed_jobs_calls
assert_nothing_raised do
assert_performed_jobs 1 do
- HelloJob.enqueue('abdelkader')
+ HelloJob.perform_later('abdelkader')
end
end
assert_nothing_raised do
assert_performed_jobs 2 do
- HelloJob.enqueue('sean')
- HelloJob.enqueue('yves')
+ HelloJob.perform_later('sean')
+ HelloJob.perform_later('yves')
end
end
end
def test_assert_performed_jobs_with_no_block
assert_nothing_raised do
- HelloJob.enqueue('rafael')
+ HelloJob.perform_later('rafael')
assert_performed_jobs 1
end
assert_nothing_raised do
- HelloJob.enqueue('aaron')
- HelloJob.enqueue('matthew')
+ HelloJob.perform_later('aaron')
+ HelloJob.perform_later('matthew')
assert_performed_jobs 3
end
end
@@ -156,7 +158,7 @@ class PerformedJobsTest < ActiveJob::TestCase
assert_nothing_raised do
assert_no_performed_jobs do
# Scheduled jobs are being enqueued in this context
- HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey')
+ HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
end
end
end
@@ -164,7 +166,7 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_jobs_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_performed_jobs 2 do
- HelloJob.enqueue('xavier')
+ HelloJob.perform_later('xavier')
end
end
@@ -174,8 +176,8 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_jobs_too_many_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_performed_jobs 1 do
- HelloJob.enqueue('cristian')
- HelloJob.enqueue('guillermo')
+ HelloJob.perform_later('cristian')
+ HelloJob.perform_later('guillermo')
end
end
@@ -185,7 +187,7 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_no_performed_jobs_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_performed_jobs do
- HelloJob.enqueue('jeremy')
+ HelloJob.perform_later('jeremy')
end
end
@@ -194,20 +196,20 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_job
assert_performed_with(job: NestedJob, queue: 'default') do
- NestedJob.enqueue
+ NestedJob.perform_later
end
end
def test_assert_performed_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
- assert_performed_with(job: LoggingJob, queue: 'default') do
- NestedJob.enqueue_at(Date.tomorrow.noon)
+ assert_performed_with(job: LoggingJob, at: Date.tomorrow.noon, queue: 'default') do
+ NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
end
end
assert_raise ActiveSupport::TestCase::Assertion do
- assert_performed_with(job: NestedJob, queue: 'low') do
- NestedJob.enqueue_at(Date.tomorrow.noon)
+ assert_performed_with(job: NestedJob, at: Date.tomorrow.noon, queue: 'low') do
+ NestedJob.set(queue: 'low', wait_until: Date.tomorrow.noon).perform_later
end
end
end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 85094387ef..ce22833b11 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -1,6 +1,7 @@
require File.expand_path('../../../load_paths', __FILE__)
require 'active_job'
+require 'support/job_buffer'
GlobalID.app = 'aj'
@@ -17,8 +18,13 @@ end
# Sidekiq doesn't work with MRI 1.9.3
exit if sidekiq? && ruby_193?
-require "adapters/#{@adapter}"
+if ENV['AJ_INTEGRATION_TESTS']
+ require 'support/integration/helper'
+else
+ require "adapters/#{@adapter}"
+end
require 'active_support/testing/autorun'
ActiveJob::Base.logger.level = Logger::DEBUG
+ActiveSupport::TestCase.test_order = :random
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
new file mode 100644
index 0000000000..779dedb53f
--- /dev/null
+++ b/activejob/test/integration/queuing_test.rb
@@ -0,0 +1,46 @@
+require 'helper'
+require 'jobs/logging_job'
+require 'active_support/core_ext/numeric/time'
+
+class QueuingTest < ActiveSupport::TestCase
+ test 'should run jobs enqueued on a listenting queue' do
+ TestJob.perform_later @id
+ wait_for_jobs_to_finish_for(5.seconds)
+ assert job_executed
+ end
+
+ test 'should not run jobs queued on a non-listenting queue' do
+ begin
+ skip if adapter_is?(:inline) || adapter_is?(:sucker_punch)
+ old_queue = TestJob.queue_name
+ TestJob.queue_as :some_other_queue
+ TestJob.perform_later @id
+ wait_for_jobs_to_finish_for(2.seconds)
+ assert_not job_executed
+ ensure
+ TestJob.queue_name = old_queue
+ end
+ end
+
+ test 'should not run job enqueued in the future' do
+ begin
+ TestJob.set(wait: 10.minutes).perform_later @id
+ wait_for_jobs_to_finish_for(5.seconds)
+ assert_not job_executed
+ rescue NotImplementedError
+ skip
+ end
+ end
+
+ test 'should run job enqueued in the future at the specified time' do
+ begin
+ TestJob.set(wait: 3.seconds).perform_later @id
+ wait_for_jobs_to_finish_for(2.seconds)
+ assert_not job_executed
+ wait_for_jobs_to_finish_for(10.seconds)
+ assert job_executed
+ rescue NotImplementedError
+ skip
+ end
+ end
+end
diff --git a/activejob/test/jobs/nested_job.rb b/activejob/test/jobs/nested_job.rb
index fd66f68991..8c4ec549a6 100644
--- a/activejob/test/jobs/nested_job.rb
+++ b/activejob/test/jobs/nested_job.rb
@@ -1,6 +1,6 @@
class NestedJob < ActiveJob::Base
def perform
- LoggingJob.enqueue "NestedJob"
+ LoggingJob.perform_later "NestedJob"
end
def job_id
diff --git a/activejob/test/jobs/rescue_job.rb b/activejob/test/jobs/rescue_job.rb
index 6b6e74e9d0..f1b9c9349e 100644
--- a/activejob/test/jobs/rescue_job.rb
+++ b/activejob/test/jobs/rescue_job.rb
@@ -6,7 +6,7 @@ class RescueJob < ActiveJob::Base
rescue_from(ArgumentError) do
JobBuffer.add('rescued from ArgumentError')
arguments[0] = "DIFFERENT!"
- retry_now
+ retry_job
end
rescue_from(ActiveJob::DeserializationError) do |e|
diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb
new file mode 100644
index 0000000000..0cda36a273
--- /dev/null
+++ b/activejob/test/support/integration/adapters/backburner.rb
@@ -0,0 +1,38 @@
+module BackburnerJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :backburner
+ Backburner.configure do |config|
+ config.logger = Rails.logger
+ end
+ unless can_run?
+ puts "Cannot run integration tests for backburner. To be able to run integration tests for backburner you need to install and start beanstalkd.\n"
+ exit
+ end
+ end
+
+ def clear_jobs
+ tube.clear
+ end
+
+ def start_workers
+ @thread = Thread.new { Backburner.work "integration-tests" } # backburner dasherizes the queue name
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+
+ def tube
+ @tube ||= Beaneater::Tube.new(Backburner::Worker.connection, "backburner.worker.queue.integration-tests") # backburner dasherizes the queue name
+ end
+
+ def can_run?
+ begin
+ Backburner::Worker.connection.send :connect!
+ rescue => e
+ return false
+ end
+ true
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/delayed_job.rb b/activejob/test/support/integration/adapters/delayed_job.rb
new file mode 100644
index 0000000000..0b591964bc
--- /dev/null
+++ b/activejob/test/support/integration/adapters/delayed_job.rb
@@ -0,0 +1,20 @@
+require 'delayed_job'
+require 'delayed_job_active_record'
+
+module DelayedJobJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :delayed_job
+ end
+ def clear_jobs
+ Delayed::Job.delete_all
+ end
+
+ def start_workers
+ @worker = Delayed::Worker.new(quiet: true, sleep_delay: 0.5, queues: %w(integration_tests))
+ @thread = Thread.new { @worker.start }
+ end
+
+ def stop_workers
+ @worker.stop
+ end
+end
diff --git a/activejob/test/support/integration/adapters/inline.rb b/activejob/test/support/integration/adapters/inline.rb
new file mode 100644
index 0000000000..83c38f706f
--- /dev/null
+++ b/activejob/test/support/integration/adapters/inline.rb
@@ -0,0 +1,15 @@
+module InlineJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :inline
+ end
+
+ def clear_jobs
+ end
+
+ def start_workers
+ end
+
+ def stop_workers
+ end
+end
+
diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb
new file mode 100644
index 0000000000..e913f04a24
--- /dev/null
+++ b/activejob/test/support/integration/adapters/qu.rb
@@ -0,0 +1,38 @@
+module QuJobsManager
+ def setup
+ require 'qu-rails'
+ require 'qu-redis'
+ ActiveJob::Base.queue_adapter = :qu
+ ENV['REDISTOGO_URL'] = "tcp://127.0.0.1:6379/12"
+ backend = Qu::Backend::Redis.new
+ backend.namespace = "active_jobs_int_test"
+ Qu.backend = backend
+ Qu.logger = Rails.logger
+ Qu.interval = 0.5
+ unless can_run?
+ puts "Cannot run integration tests for qu. To be able to run integration tests for qu you need to install and start redis.\n"
+ exit
+ end
+ end
+
+ def clear_jobs
+ Qu.clear "integration_tests"
+ end
+
+ def start_workers
+ @thread = Thread.new { Qu::Worker.new("integration_tests").start }
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+
+ def can_run?
+ begin
+ Qu.backend.connection.client.connect
+ rescue => e
+ return false
+ end
+ true
+ end
+end
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
new file mode 100644
index 0000000000..ba7657a42a
--- /dev/null
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -0,0 +1,37 @@
+module QueJobsManager
+ def setup
+ require 'sequel'
+ ActiveJob::Base.queue_adapter = :que
+ que_url = ENV['QUE_DATABASE_URL'] || 'postgres:///active_jobs_que_int_test'
+ uri = URI.parse(que_url)
+ user = uri.user||ENV['USER']
+ pass = uri.password
+ db = uri.path[1..-1]
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ Que.connection = Sequel.connect(que_url)
+ Que.migrate!
+ Que.mode = :off
+ Que.worker_count = 1
+ rescue Sequel::DatabaseConnectionError
+ puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
+ exit
+ end
+
+ def clear_jobs
+ Que.clear!
+ end
+
+ def start_workers
+ @thread = Thread.new do
+ loop do
+ Que::Job.work("integration_tests")
+ sleep 0.5
+ end
+ end
+ end
+
+ def stop_workers
+ @thread.kill
+ end
+end
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
new file mode 100644
index 0000000000..038473ccdc
--- /dev/null
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -0,0 +1,33 @@
+module QueueClassicJobsManager
+ def setup
+ ENV['QC_DATABASE_URL'] ||= 'postgres:///active_jobs_qc_int_test'
+ ENV['QC_LISTEN_TIME'] = "0.5"
+ uri = URI.parse(ENV['QC_DATABASE_URL'])
+ user = uri.user||ENV['USER']
+ pass = uri.password
+ db = uri.path[1..-1]
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ ActiveJob::Base.queue_adapter = :queue_classic
+ QC::Setup.create
+ rescue PG::ConnectionBad
+ puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
+ exit
+ end
+
+ def clear_jobs
+ QC::Queue.new("integration_tests").delete_all
+ end
+
+ def start_workers
+ QC::Conn.disconnect
+ @pid = fork do
+ worker = QC::Worker.new(q_name: 'integration_tests')
+ worker.start
+ end
+ end
+
+ def stop_workers
+ Process.kill 'HUP', @pid
+ end
+end
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
new file mode 100644
index 0000000000..9de3e7c879
--- /dev/null
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -0,0 +1,49 @@
+module ResqueJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :resque
+ Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "tcp://127.0.0.1:6379/12", :thread_safe => true)
+ Resque.logger = Rails.logger
+ unless can_run?
+ puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n"
+ exit
+ end
+ end
+
+ def clear_jobs
+ Resque.queues.each { |queue_name| Resque.redis.del "queue:#{queue_name}" }
+ Resque.redis.keys("delayed:*").each { |key| Resque.redis.del "#{key}" }
+ Resque.redis.del "delayed_queue_schedule"
+ end
+
+ def start_workers
+ @resque_thread = Thread.new do
+ w = Resque::Worker.new("integration_tests")
+ w.term_child = true
+ w.work(0.5)
+ end
+ @scheduler_thread = Thread.new do
+ Resque::Scheduler.configure do |c|
+ c.poll_sleep_amount = 0.5
+ c.dynamic = true
+ c.quiet = true
+ c.logfile = nil
+ end
+ Resque::Scheduler.master_lock.release!
+ Resque::Scheduler.run
+ end
+ end
+
+ def stop_workers
+ @resque_thread.kill
+ @scheduler_thread.kill
+ end
+
+ def can_run?
+ begin
+ Resque.redis.client.connect
+ rescue => e
+ return false
+ end
+ true
+ end
+end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
new file mode 100644
index 0000000000..0a0a549c9e
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -0,0 +1,57 @@
+require 'sidekiq/cli'
+require 'sidekiq/api'
+
+module SidekiqJobsManager
+
+ def setup
+ ActiveJob::Base.queue_adapter = :sidekiq
+ unless can_run?
+ puts "Cannot run integration tests for sidekiq. To be able to run integration tests for sidekiq you need to install and start redis.\n"
+ exit
+ end
+ end
+
+ def clear_jobs
+ Sidekiq::ScheduledSet.new.clear
+ Sidekiq::Queue.new("integration_tests").clear
+ end
+
+ def start_workers
+ fork do
+ sidekiq = Sidekiq::CLI.instance
+ logfile = Rails.root.join("log/sidekiq.log").to_s
+ pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
+ sidekiq.parse([ "--require", Rails.root.to_s,
+ "--queue", "integration_tests",
+ "--logfile", logfile,
+ "--pidfile", pidfile,
+ "--environment", "test",
+ "--concurrency", "1",
+ "--timeout", "1",
+ "--daemon",
+ ])
+ require 'celluloid'
+ require 'sidekiq/scheduled'
+ Sidekiq.poll_interval = 0.5
+ Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
+ sidekiq.run
+ end
+ sleep 1
+ end
+
+ def stop_workers
+ pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
+ Process.kill 'TERM', File.open(pidfile).read.to_i
+ FileUtils.rm_f pidfile
+ rescue
+ end
+
+ def can_run?
+ begin
+ Sidekiq.redis { |conn| conn.connect }
+ rescue => e
+ return false
+ end
+ true
+ end
+end
diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb
new file mode 100644
index 0000000000..f21bb38a32
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sneakers.rb
@@ -0,0 +1,90 @@
+require 'sneakers/runner'
+require 'sneakers/publisher'
+require 'timeout'
+
+module Sneakers
+ class Publisher
+ def safe_ensure_connected
+ @mutex.synchronize do
+ ensure_connection! unless connected?
+ end
+ end
+ end
+end
+
+
+module SneakersJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :sneakers
+ Sneakers.configure :heartbeat => 2,
+ :amqp => 'amqp://guest:guest@localhost:5672',
+ :vhost => '/',
+ :exchange => 'active_jobs_sneakers_int_test',
+ :exchange_type => :direct,
+ :daemonize => true,
+ :threads => 1,
+ :workers => 1,
+ :pid_path => Rails.root.join("tmp/sneakers.pid").to_s,
+ :log => Rails.root.join("log/sneakers.log").to_s
+ unless can_run?
+ puts "Cannot run integration tests for sneakers. To be able to run integration tests for sneakers you need to install and start rabbitmq.\n"
+ exit
+ end
+ end
+
+ def clear_jobs
+ bunny_queue.purge
+ end
+
+ def start_workers
+ @pid = fork do
+ queues = %w(integration_tests)
+ workers = queues.map do |q|
+ worker_klass = "ActiveJobWorker"+Digest::MD5.hexdigest(q)
+ Sneakers.const_set(worker_klass, Class.new(ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper) do
+ from_queue q
+ end)
+ end
+ Sneakers::Runner.new(workers).run
+ end
+ begin
+ Timeout.timeout(10) do
+ while bunny_queue.status[:consumer_count] == 0
+ sleep 0.5
+ end
+ end
+ rescue Timeout::Error
+ stop_workers
+ raise "Failed to start sneakers worker"
+ end
+ end
+
+ def stop_workers
+ Process.kill 'TERM', @pid
+ Process.kill 'TERM', File.open(Rails.root.join("tmp/sneakers.pid").to_s).read.to_i
+ rescue
+ end
+
+ def can_run?
+ begin
+ bunny_publisher
+ rescue => e
+ return false
+ end
+ true
+ end
+
+ protected
+ def bunny_publisher
+ @bunny_publisher ||= begin
+ p = ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper.send(:publisher)
+ p.safe_ensure_connected
+ p
+ end
+ end
+
+ def bunny_queue
+ @queue ||= bunny_publisher.exchange.channel.queue "integration_tests", durable: true
+ end
+
+end
diff --git a/activejob/test/support/integration/adapters/sucker_punch.rb b/activejob/test/support/integration/adapters/sucker_punch.rb
new file mode 100644
index 0000000000..9c0d66b469
--- /dev/null
+++ b/activejob/test/support/integration/adapters/sucker_punch.rb
@@ -0,0 +1,6 @@
+module SuckerPunchJobsManager
+ def setup
+ ActiveJob::Base.queue_adapter = :sucker_punch
+ SuckerPunch.logger = nil
+ end
+end
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
new file mode 100644
index 0000000000..65994d6a1c
--- /dev/null
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -0,0 +1,21 @@
+if ENV['AJADAPTER'] == 'delayed_job'
+ generate "delayed_job:active_record", "--quiet"
+ rake("db:migrate")
+end
+
+initializer 'activejob.rb', <<-CODE
+require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
+JobsManager.current_manager.setup
+CODE
+
+file 'app/jobs/test_job.rb', <<-CODE
+class TestJob < ActiveJob::Base
+ queue_as :integration_tests
+
+ def perform(x)
+ File.open(Rails.root.join("tmp/\#{x}"), "w+") do |f|
+ f.write x
+ end
+ end
+end
+CODE
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
new file mode 100644
index 0000000000..9bd45e09e8
--- /dev/null
+++ b/activejob/test/support/integration/helper.rb
@@ -0,0 +1,30 @@
+puts "*** rake aj:integration:#{ENV['AJADAPTER']} ***\n"
+
+ENV["RAILS_ENV"] = "test"
+ActiveJob::Base.queue_name_prefix = nil
+
+require 'rails/generators/rails/app/app_generator'
+
+dummy_app_path = Dir.mktmpdir + "/dummy"
+dummy_app_template = File.expand_path("../dummy_app_template.rb", __FILE__)
+args = Rails::Generators::ARGVScrubber.new(["new", dummy_app_path, "--skip-gemfile", "--skip-bundle",
+ "--skip-git", "--skip-spring", "-d", "sqlite3", "--skip-javascript", "--force", "--quiet",
+ "--template", dummy_app_template]).prepare!
+Rails::Generators::AppGenerator.start args
+
+require "#{dummy_app_path}/config/environment.rb"
+
+ActiveRecord::Migrator.migrations_paths = [ Rails.root.join('db/migrate').to_s ]
+require 'rails/test_help'
+
+Rails.backtrace_cleaner.remove_silencers!
+
+require_relative 'test_case_helpers'
+ActiveSupport::TestCase.send(:include, TestCaseHelpers)
+
+JobsManager.current_manager.start_workers
+
+Minitest.after_run do
+ JobsManager.current_manager.stop_workers
+ JobsManager.current_manager.clear_jobs
+end
diff --git a/activejob/test/support/integration/jobs_manager.rb b/activejob/test/support/integration/jobs_manager.rb
new file mode 100644
index 0000000000..4df34aaeb1
--- /dev/null
+++ b/activejob/test/support/integration/jobs_manager.rb
@@ -0,0 +1,27 @@
+class JobsManager
+ @@managers = {}
+ attr :adapter_name
+
+ def self.current_manager
+ @@managers[ENV['AJADAPTER']] ||= new(ENV['AJADAPTER'])
+ end
+
+ def initialize(adapter_name)
+ @adapter_name = adapter_name
+ require_relative "adapters/#{adapter_name}"
+ extend "#{adapter_name.camelize}JobsManager".constantize
+ end
+
+ def setup
+ ActiveJob::Base.queue_adapter = nil
+ end
+
+ def clear_jobs
+ end
+
+ def start_workers
+ end
+
+ def stop_workers
+ end
+end
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
new file mode 100644
index 0000000000..ee2f6aebea
--- /dev/null
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -0,0 +1,48 @@
+require 'active_support/concern'
+require 'support/integration/jobs_manager'
+
+module TestCaseHelpers
+ extend ActiveSupport::Concern
+
+ included do
+ self.use_transactional_fixtures = false
+
+ setup do
+ clear_jobs
+ @id = "AJ-#{SecureRandom.uuid}"
+ end
+
+ teardown do
+ clear_jobs
+ end
+ end
+
+ protected
+
+ def jobs_manager
+ JobsManager.current_manager
+ end
+
+ def clear_jobs
+ jobs_manager.clear_jobs
+ end
+
+ def adapter_is?(adapter)
+ ActiveJob::Base.queue_adapter.name.split("::").last.gsub(/Adapter$/, '').underscore==adapter.to_s
+ end
+
+ def wait_for_jobs_to_finish_for(seconds=60)
+ begin
+ Timeout.timeout(seconds) do
+ while !job_executed do
+ sleep 0.25
+ end
+ end
+ rescue Timeout::Error
+ end
+ end
+
+ def job_executed
+ Dummy::Application.root.join("tmp/#{@id}").exist?
+ end
+end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index c14b0688c7..5588699d9b 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -12,18 +12,21 @@
* Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
- These methods may cause confusion with the `reset_changes` that behaves differently
- of them.
+ These methods may cause confusion with the `reset_changes`, which has
+ different behaviour.
+
+ *Rafael Mendonça França*
* Deprecate `ActiveModel::Dirty#reset_changes` in favor of `#clear_changes_information`.
- This method name is causing confusion with the `reset_#{attribute}`
- methods. While `reset_name` set the value of the name attribute for the
- previous value `reset_changes` only discard the changes and previous
- changes.
+ Method's name is causing confusion with the `reset_#{attribute}` methods.
+ While `reset_name` sets the value of the name attribute to previous value
+ `reset_changes` only discards the changes.
+
+ *Rafael Mendonça França*
-* Added `restore_attributes` method to `ActiveModel::Dirty` API to restore all the
- changed values to the previous data.
+* Added `restore_attributes` method to `ActiveModel::Dirty` API which restores
+ the value of changed attributes to previous value.
*Igor G.*
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 1e90305a8c..407dda2ec3 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -6,7 +6,7 @@ task :default => :test
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
+ t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
t.warning = true
t.verbose = true
end
diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb
deleted file mode 100644
index b8e74acd5e..0000000000
--- a/activemodel/examples/validations.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require File.expand_path('../../../load_paths', __FILE__)
-require 'active_model'
-
-class Person
- include ActiveModel::Conversion
- include ActiveModel::Validations
-
- validates :name, presence: true
-
- attr_accessor :name
-
- def initialize(attributes = {})
- @name = attributes[:name]
- end
-
- def persist
- @persisted = true
- end
-
- def persisted?
- @persisted
- end
-end
-
-person1 = Person.new
-p person1.valid? # => false
-p person1.errors.messages # => {:name=>["can't be blank"]}
-
-person2 = Person.new(name: 'matz')
-p person2.valid? # => true
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index dd652190f7..e37edcf581 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveModel
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index f6ad35769f..8f2a069ba3 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -75,7 +75,7 @@ module ActiveModel
end
validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
- validates_confirmation_of :password, if: ->{ password.present? }
+ validates_confirmation_of :password, allow_blank: true
end
# This code is necessary as long as the protected_attributes gem is supported.
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 7ee033ba5f..60439f5631 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -86,6 +86,8 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless].freeze
+
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
@@ -144,7 +146,11 @@ module ActiveModel
options = args.extract_options!
if args.all? { |arg| arg.is_a?(Symbol) }
- options.assert_valid_keys([:on, :if, :unless])
+ options.each_key do |k|
+ unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
+ end
+ end
end
if options.key?(:on)
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 5e80353836..4ce6103593 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -17,4 +17,4 @@ require 'mocha/setup' # FIXME: stop using mocha
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index d876f73052..de71bb6f42 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -167,10 +167,12 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_invalid_options_to_validate
- assert_raises(ArgumentError) do
+ error = assert_raises(ArgumentError) do
# A common mistake -- we meant to call 'validates'
Topic.validate :title, presence: true
end
+ message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless. Perhaps you meant to call `validates` instead of `validate`?'
+ assert_equal message, error.message
end
def test_errors_conversions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index be47ef75e7..210151a2ad 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,70 @@
+* Honor overridden `rack.test` in Rack environment for the connection
+ management middleware.
+
+ *Simon Eskildsen*
+
+* Add a truncate method to the connection.
+
+ *Aaron Patterson*
+
+* Don't autosave unchanged has_one through records.
+
+ *Alan Kennedy*, *Steve Parrington*
+
+* Do not dump foreign keys for ignored tables.
+
+ *Yves Senn*
+
+* PostgreSQL adapter correctly dumps foreign keys targeting tables
+ outside the schema search path.
+
+ Fixes #16907.
+
+ *Matthew Draper*, *Yves Senn*
+
+* When a thread is killed, rollback the active transaction, instead of
+ committing it during the stack unwind. Previously, we could commit half-
+ completed work. This fix only works for Ruby 2.0+; on 1.9, we can't
+ distinguish a thread kill from an ordinary non-local (block) return, so must
+ default to committing.
+
+ *Chris Hanks*
+
+* A `NullRelation` should represent nothing. This fixes a bug where
+ `Comment.where(post_id: Post.none)` returned a non-empty result.
+
+ Fixes #15176.
+
+ *Matthew Draper*, *Yves Senn*
+
+* Include default column limits in schema.rb. Allows defaults to be changed
+ in the future without affecting old migrations that assumed old defaults.
+
+ *Jeremy Kemper*
+
+* MySQL: schema.rb now includes TEXT and BLOB column limits.
+
+ *Jeremy Kemper*
+
+* MySQL: correct LONGTEXT and LONGBLOB limits from 2GB to their true 4GB.
+
+ *Jeremy Kemper*
+
+* SQLite3Adapter now checks for views in `table_exists?`. Fixes #14041.
+
+ *Girish Sonawane*
+
+* Introduce `connection.supports_views?` to check whether the current adapter
+ has support for SQL views. Connection adapters should define this method.
+
+ *Yves Senn*
+
+* Allow included modules to override association methods.
+
+ Fixes #16684.
+
+ *Yves Senn*
+
* Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain
the database connection to the current environment.
@@ -35,21 +102,22 @@
*Agis Anastasopoulos*
-* Fixed the `Relation#exists?` to work with polymorphic associations.
+* Fixed `Relation#exists?` to work with polymorphic associations.
Fixes #15821.
*Kassio Borges*
-* Currently, Active Record will rescue any errors raised within
- after_rollback/after_create callbacks and print them to the logs. Next versions of rails
- will not rescue those errors anymore, and just bubble them up, as the other callbacks.
+* Currently, Active Record rescues any errors raised within
+ `after_rollback`/`after_create` callbacks and prints them to the logs.
+ Future versions of Rails will not rescue these errors anymore and
+ just bubble them up like the other callbacks.
- This adds a opt-in flag to enable that behaviour, of not rescuing the errors.
+ This commit adds an opt-in flag to enable not rescuing the errors.
Example:
- # For not swallow errors in after_commit/after_rollback callbacks.
+ # Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
Fixes #13460.
@@ -69,7 +137,7 @@
*Sean Griffin*
-* Fix regression on after_commit that didnt fire when having nested transactions.
+* Fix regression on `after_commit` that did not fire with nested transactions.
Fixes #16425.
@@ -105,9 +173,9 @@
* Define `id_was` to get the previous value of the primary key.
- Currently when we call id_was and we have a custom primary key name
+ Currently when we call `id_was` and we have a custom primary key name,
Active Record will return the current value of the primary key. This
- make impossible to correctly do an update operation if you change the
+ makes it impossible to correctly do an update operation if you change the
id.
Fixes #16413.
@@ -139,7 +207,7 @@
*Eileen M. Uchtitelle*, *Aaron Patterson*
-* No verbose backtrace by db:drop when database does not exist.
+* No verbose backtrace by `db:drop` when database does not exist.
Fixes #16295.
@@ -184,7 +252,7 @@
*Stefan Kanev*
-* Dont swallow errors on compute_type when having a bad alias_method on
+* Do not swallow errors on `compute_type` when having a bad `alias_method` on
a class.
*arthurnn*
@@ -267,7 +335,7 @@
*Eileen M. Uchitelle, Aaron Patterson*
-* Avoid type casting boolean and ActiveSupport::Duration values to numeric
+* Avoid type casting boolean and `ActiveSupport::Duration` values to numeric
values for string columns. Otherwise, in some database, the string column
values will be coerced to a numeric allowing false or 0.seconds match any
string starting with a non-digit.
@@ -298,8 +366,8 @@
*Abdelkader Boudih*
-* Move 'dependent: :destroy' handling for 'belongs_to'
- from 'before_destroy' to 'after_destroy' callback chain
+* Move 'dependent: :destroy' handling for `belongs_to`
+ from `before_destroy` to `after_destroy` callback chain
Fixes #12380.
@@ -307,8 +375,9 @@
* Detect in-place modifications on String attributes.
- Before this change user have to mark the attribute as changed to it be persisted
- in the database. Now it is not required anymore.
+ Before this change, an attribute modified in-place had to be marked as
+ changed in order for it to be persisted in the database. Now it is no longer
+ required.
Before:
@@ -388,14 +457,6 @@
*Sean Griffin*
-* `has_many :through` associations will no longer save the through record
- twice when added in an `after_create` callback defined before the
- associations.
-
- Fixes #3798.
-
- *Sean Griffin*
-
* Detect in-place modifications of PG array types
*Sean Griffin*
@@ -428,7 +489,7 @@
*Sean Griffin*
-* Pluck now works when selecting columns from different tables with the same
+* `Pluck` now works when selecting columns from different tables with the same
name.
Fixes #15649.
@@ -495,7 +556,7 @@
*Sean Griffin*
-* Implemented ActiveRecord::Base#pretty_print to work with PP.
+* Implemented `ActiveRecord::Base#pretty_print` to work with PP.
*Ethan*
@@ -536,16 +597,16 @@
*Arun Agrawal*
-* Fix redefine a has_and_belongs_to_many inside inherited class
- Fixing regression case, where redefining the same has_an_belongs_to_many
+* Fix redefine a `has_and_belongs_to_many` inside inherited class
+ Fixing regression case, where redefining the same `has_and_belongs_to_many`
definition into a subclass would raise.
Fixes #14983.
*arthurnn*
-* Fix has_and_belongs_to_many public reflection.
- When defining a has_and_belongs_to_many, internally we convert that to two has_many.
+* Fix `has_and_belongs_to_many` public reflection.
+ When defining a `has_and_belongs_to_many`, internally we convert that to two has_many.
But as `reflections` is a public API, people expect to see the right macro.
Fixes #14682.
@@ -582,7 +643,7 @@
*Brock Trappitt*
-* Fixed the inferred table name of a has_and_belongs_to_many auxiliar
+* Fixed the inferred table name of a `has_and_belongs_to_many` auxiliar
table inside a schema.
Fixes #14824.
@@ -626,7 +687,7 @@
*Aaron Nelson*
-* Fix how to calculate associated class name when using namespaced has_and_belongs_to_many
+* Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many`
association.
Fixes #14709.
@@ -696,7 +757,7 @@
* Fixed has_and_belongs_to_many's CollectionAssociation size calculation.
- has_and_belongs_to_many should fall back to using the normal CollectionAssociation's
+ `has_and_belongs_to_many` should fall back to using the normal CollectionAssociation's
size calculation if the collection is not cached or loaded.
Fixes #14913, #14914.
@@ -756,7 +817,7 @@
*Timur Alperovich*
-* Give ActiveRecord::PredicateBuilder private methods the privacy they deserve.
+* Give `ActiveRecord::PredicateBuilder` private methods the privacy they deserve.
*Hector Satre*
@@ -812,7 +873,7 @@
*Kuldeep Aggarwal*
-* Fixed has_many association to make it support irregular inflections.
+* Fixed `has_many` association to make it support irregular inflections.
Fixes #8928.
@@ -926,7 +987,7 @@
* `to_sql` on an association now matches the query that is actually executed, where it
could previously have incorrectly accrued additional conditions (e.g. as a result of
- a previous query). CollectionProxy now always defers to the association scope's
+ a previous query). `CollectionProxy` now always defers to the association scope's
`arel` method so the (incorrect) inherited one should be entirely concealed.
Fixes #14003.
@@ -1157,7 +1218,7 @@
The current solution of incrementing the beginning is not correct and is now
deprecated. For subtypes where we don't know how to increment (e.g. `#succ`
- is not defined) it will raise an ArgumentException for ranges with excluding
+ is not defined) it will raise an `ArgumentException` for ranges with excluding
beginnings.
*Yves Senn*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 4d5178ff02..b1069e5dcc 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -51,7 +51,7 @@ end
t.libs << 'test'
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|x| x =~ /\/adapters\//
- } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb"))
+ } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
t.warning = true
t.verbose = true
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 18da28d480..12ca3a48a9 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1171,7 +1171,7 @@ module ActiveRecord
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
# association will use "person_id" as the default <tt>:foreign_key</tt>.
# [:primary_key]
- # Specify the method that returns the primary key used for the association. By default this is +id+.
+ # Specify the name of the column to use as the primary key for the association. By default this is +id+.
# [:dependent]
# Controls what happens to the associated objects when
# their owner is destroyed. Note that these are implemented as
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 26fed22b4c..b965230e60 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- def next_chain_scope(scope, table, reflection, chain, tracker, assoc_klass, foreign_table, next_reflection)
+ def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
join_keys = reflection.join_keys(assoc_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -135,16 +135,16 @@ module ActiveRecord
tables = construct_tables(chain, assoc_klass, refl, tracker)
- reflection = chain.last
+ owner_reflection = chain.last
table = tables.last
- scope = last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
+ scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
unless reflection == chain.last
next_reflection = chain[i + 1]
- scope = next_chain_scope(scope, table, reflection, chain, tracker, assoc_klass, foreign_table, next_reflection)
+ scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
end
is_first_chain = i == 0
@@ -188,11 +188,7 @@ module ActiveRecord
end
def eval_scope(klass, scope, owner)
- if scope.is_a?(Relation)
- scope
- else
- klass.unscoped.instance_exec(owner, &scope)
- end
+ klass.unscoped.instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 065a2cff01..1836ff0910 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -407,7 +407,7 @@ module ActiveRecord
private
def get_records
- return scope.to_a if reflection.scope_chain.any?(&:any?)
+ return scope.to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index c3bbdccad8..e7d3c9ba40 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -65,7 +65,7 @@ module ActiveRecord
if reflection.type
value = foreign_klass.base_class.name
- column = klass.columns_hash[column.to_s]
+ column = klass.columns_hash[reflection.type.to_s]
substitute = klass.connection.substitute_at(column, bind_values.length)
bind_values.push [column, value]
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 7519fec10a..46bccbf15a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -2,33 +2,42 @@ module ActiveRecord
module Associations
# Implements the details of eager loading of Active Record associations.
#
- # Note that 'eager loading' and 'preloading' are actually the same thing.
- # However, there are two different eager loading strategies.
+ # Suppose that you have the following two Active Record models:
#
- # The first one is by using table joins. This was only strategy available
- # prior to Rails 2.1. Suppose that you have an Author model with columns
- # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
- # this strategy, Active Record would try to retrieve all data for an author
- # and all of its books via a single query:
+ # class Author < ActiveRecord::Base
+ # # columns: name, age
+ # has_many :books
+ # end
#
- # SELECT * FROM authors
- # LEFT OUTER JOIN books ON authors.id = books.author_id
- # WHERE authors.name = 'Ken Akamatsu'
+ # class Book < ActiveRecord::Base
+ # # columns: title, sales
+ # end
#
- # However, this could result in many rows that contain redundant data. After
- # having received the first row, we already have enough data to instantiate
- # the Author object. In all subsequent rows, only the data for the joined
- # 'books' table is useful; the joined 'authors' data is just redundant, and
- # processing this redundant data takes memory and CPU time. The problem
- # quickly becomes worse and worse as the level of eager loading increases
- # (i.e. if Active Record is to eager load the associations' associations as
- # well).
+ # When you load an author with all associated books Active Record will make
+ # multiple queries like this:
+ #
+ # Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
+ #
+ # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
+ # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
+ #
+ # Active Record saves the ids of the records from the first query to use in
+ # the second. Depending on the number of associations involved there can be
+ # arbitrarily many SQL queries made.
+ #
+ # However, if there is a WHERE clause that spans across tables Active
+ # Record will fall back to a slightly more resource-intensive single query:
+ #
+ # Author.includes(:books).where(books: {title: 'Illiad'}).to_a
+ # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
+ # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
+ # FROM `authors`
+ # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
+ # WHERE `books`.`title` = 'Illiad'
+ #
+ # This could result in many rows that contain redundant data and it performs poorly at scale
+ # and is therefore only used when necessary.
#
- # The second strategy is to use multiple database queries, one for each
- # level of association. Since Rails 2.1, this is the default strategy. In
- # situations where a table join is necessary (e.g. when the +:conditions+
- # option references an association's column), it will fallback to the table
- # join strategy.
class Preloader #:nodoc:
extend ActiveSupport::Autoload
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index f2e3a4e40f..b9326b9683 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def get_records
- return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?)
+ return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ceee96b3a8..f4a4e3f605 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -31,7 +31,7 @@ module ActiveRecord
end
}
- BLACKLISTED_CLASS_METHODS = %w(private public protected)
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
class AttributeMethodCache
def initialize
@@ -69,6 +69,8 @@ module ActiveRecord
@generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
@attribute_methods_generated = false
include @generated_attribute_methods
+
+ super
end
# Generates all the attribute related methods for columns in the database
@@ -109,7 +111,7 @@ module ActiveRecord
# # => false
def instance_method_already_implemented?(method_name)
if dangerous_attribute_method?(method_name)
- raise DangerousAttributeError, "#{method_name} is defined by Active Record"
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
end
if superclass == Base
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index f439bd1ffe..b7fe079ef5 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,8 @@ module ActiveRecord
module AttributeMethods
module TimeZoneConversion
class TimeZoneConverter < SimpleDelegator # :nodoc:
+ include Type::Decorator
+
def type_cast_from_database(value)
convert_time_to_time_zone(super)
end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 1e146a07da..d4a787f2fe 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -23,8 +23,11 @@ module ActiveRecord
end
def add_uninitialized_attributes(attributes)
- types.except(*attributes.keys).each do |name, type|
- attributes[name] = Attribute.uninitialized(name, type)
+ types.each_key do |name|
+ next if attributes.key? name
+ type = types[name]
+ attributes[name] =
+ Attribute.uninitialized(name, type)
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index dd92e29199..c384e8c413 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -338,7 +338,6 @@ module ActiveRecord
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
-
if autosave
records_to_destroy = records.select(&:marked_for_destruction?)
records_to_destroy.each { |record| association.destroy(record) }
@@ -362,7 +361,6 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
- @new_record_before_save = false
end
# reconstruct the scope now that we know the owner's id
@@ -405,7 +403,9 @@ module ActiveRecord
# If the record is new or it has changed, returns true.
def record_changed?(reflection, record, key)
- record.new_record? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
+ record.new_record? ||
+ (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
+ record.attribute_changed?(reflection.foreign_key)
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
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 bc1a670b42..9760729da3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -640,7 +640,7 @@ module ActiveRecord
end
def call(env)
- testing = env.key?('rack.test')
+ testing = env['rack.test']
response = @app.call(env)
response[2] = ::Rack::BodyProxy.new(response[2]) do
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 98e96099cb..ff8b3e9890 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -83,6 +83,11 @@ module ActiveRecord
exec_query(sql, name, binds)
end
+ # Executes the truncate statement.
+ def truncate(table_name, name = nil)
+ raise NotImplementedError
+ end
+
# Executes update +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 9bd0401e40..b05a4f8440 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -19,12 +19,16 @@ module ActiveRecord
spec = {}
spec[:name] = column.name.inspect
spec[:type] = column.type.to_s
- spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit]
+ spec[:null] = 'false' unless column.null
+
+ limit = column.limit || types[column.type][:limit]
+ spec[:limit] = limit.inspect if limit
spec[:precision] = column.precision.inspect if column.precision
spec[:scale] = column.scale.inspect if column.scale
- spec[:null] = 'false' unless column.null
- spec[:default] = schema_default(column) if column.has_default?
- spec.delete(:default) if spec[:default].nil?
+
+ default = schema_default(column) if column.has_default?
+ spec[:default] = default unless default.nil?
+
spec
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 90be835d8a..fd666c8c39 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -190,11 +190,17 @@ module ActiveRecord
rollback_transaction if transaction
raise
ensure
- begin
- commit_transaction unless error
- rescue Exception
- transaction.rollback unless transaction.state.completed?
- raise
+ unless error
+ if Thread.current.status == 'aborting'
+ rollback_transaction
+ else
+ begin
+ commit_transaction
+ rescue Exception
+ transaction.rollback unless transaction.state.completed?
+ raise
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index a1b6671664..a0d9086875 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -66,6 +66,7 @@ module ActiveRecord
# Most of the methods in the adapter are useful during migrations. Most
# notably, the instance methods provided by SchemaStatement are very useful.
class AbstractAdapter
+ ADAPTER_NAME = 'Abstract'.freeze
include Quoting, DatabaseStatements, SchemaStatements
include DatabaseLimits
include QueryCache
@@ -167,7 +168,7 @@ module ActiveRecord
# Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
- 'Abstract'
+ self.class::ADAPTER_NAME
end
# Does this adapter support migrations?
@@ -239,6 +240,11 @@ module ActiveRecord
false
end
+ # Does this adapter support views?
+ def supports_views?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 1a76beb3f3..e4cfe843a8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -162,10 +162,6 @@ module ActiveRecord
end
end
- def adapter_name #:nodoc:
- self.class::ADAPTER_NAME
- end
-
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -201,6 +197,10 @@ module ActiveRecord
true
end
+ def supports_views?
+ version[0] >= 5
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -389,6 +389,10 @@ module ActiveRecord
end
end
+ def truncate(table_name, name = nil)
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
+ end
+
def table_exists?(name)
return false unless name.present?
return true if tables(nil, nil, name).any?
@@ -640,18 +644,21 @@ module ActiveRecord
def initialize_type_map(m) # :nodoc:
super
+
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
Type::String.new(limit: limit)
end
- m.register_type %r(tinytext)i, Type::Text.new(limit: 255)
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 255)
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 16777215)
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 16777215)
- m.register_type %r(longtext)i, Type::Text.new(limit: 2147483647)
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2147483647)
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
m.register_type %r(^bigint)i, Type::Integer.new(limit: 8)
m.register_type %r(^int)i, Type::Integer.new(limit: 4)
m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3)
@@ -783,10 +790,6 @@ module ActiveRecord
full_version =~ /mariadb/i
end
- def supports_views?
- version[0] >= 5
- end
-
def supports_rename_index?
mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 39d52e6349..38bdddefba 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -29,7 +29,7 @@ module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
- ADAPTER_NAME = 'Mysql2'
+ ADAPTER_NAME = 'Mysql2'.freeze
def initialize(connection, logger, connection_options, config)
super
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index a03bc28744..da3aecf69a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -66,7 +66,7 @@ module ActiveRecord
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
#
class MysqlAdapter < AbstractMysqlAdapter
- ADAPTER_NAME = 'MySQL'
+ ADAPTER_NAME = 'MySQL'.freeze
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max = 1000)
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 767b6b614a..799aafbd99 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -60,8 +60,8 @@ module ActiveRecord
def create_database(name, options = {})
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
- option_string = options.sum do |key, value|
- case key
+ option_string = options.inject("") do |memo, (key, value)|
+ memo += case key
when :owner
" OWNER = \"#{value}\""
when :template
@@ -466,7 +466,7 @@ module ActiveRecord
def foreign_keys(table_name)
fk_info = select_all <<-SQL.strip_heredoc
- SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -488,6 +488,7 @@ module ActiveRecord
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
options[:on_update] = extract_foreign_key_action(row['on_update'])
+
ForeignKeyDefinition.new(table_name, row['to_table'], options)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index eede374678..0d74cb6707 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -74,7 +74,7 @@ module ActiveRecord
# In addition, default connection parameters of libpq can be set per environment variables.
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
- ADAPTER_NAME = 'PostgreSQL'
+ ADAPTER_NAME = 'PostgreSQL'.freeze
NATIVE_DATABASE_TYPES = {
primary_key: "serial primary key",
@@ -118,11 +118,6 @@ module ActiveRecord
include PostgreSQL::DatabaseStatements
include Savepoints
- # Returns 'PostgreSQL' as adapter name for identification purposes.
- def adapter_name
- ADAPTER_NAME
- end
-
def schema_creation # :nodoc:
PostgreSQL::SchemaCreation.new self
end
@@ -163,6 +158,10 @@ module ActiveRecord
true
end
+ def supports_views?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -256,6 +255,10 @@ module ActiveRecord
@statements.clear
end
+ def truncate(table_name, name = nil)
+ exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
+ end
+
# Is this connection alive and ready for queries?
def active?
@connection.query 'SELECT 1'
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index faf1cdc686..ebb311df57 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -67,6 +67,7 @@ module ActiveRecord
#
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
+ ADAPTER_NAME = 'SQLite'.freeze
include Savepoints
NATIVE_DATABASE_TYPES = {
@@ -147,10 +148,6 @@ module ActiveRecord
end
end
- def adapter_name #:nodoc:
- 'SQLite'
- end
-
def supports_ddl_transactions?
true
end
@@ -186,6 +183,10 @@ module ActiveRecord
true
end
+ def supports_views?
+ true
+ end
+
def active?
@active != false
end
@@ -372,7 +373,7 @@ module ActiveRecord
sql = <<-SQL
SELECT name
FROM sqlite_master
- WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
SQL
sql << " AND name = #{quote_table_name(table_name)}" if table_name
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 82b9c79533..069aa977bf 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -124,6 +124,8 @@ module ActiveRecord
def find(*ids)
# We don't have cache keys for this stuff yet
return super unless ids.length == 1
+ # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
+ return super if ids.first.kind_of?(Symbol)
return super if block_given? ||
primary_key.nil? ||
default_scopes.any? ||
@@ -152,6 +154,7 @@ module ActiveRecord
def find_by(*args)
return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
+ return super if default_scopes.any?
hash = args.first
@@ -159,6 +162,9 @@ module ActiveRecord
v.nil? || Array === v || Hash === v
}
+ # We can't cache Post.find_by(author: david) ...yet
+ return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
+
key = hash.keys
klass = self
@@ -178,12 +184,10 @@ module ActiveRecord
end
def find_by!(*args)
- find_by(*args) or raise RecordNotFound
+ find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
end
def initialize_generated_modules
- super
-
generated_association_methods
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index f0b6afc4b4..73fd96f979 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -20,7 +20,7 @@ module ActiveRecord
def reset_counters(id, *counters)
object = find(id)
counters.each do |counter_association|
- has_many_association = _reflect_on_association(counter_association.to_sym)
+ has_many_association = _reflect_on_association(counter_association)
unless has_many_association
has_many = reflect_on_all_associations(:has_many)
has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index a85ba6b539..4044f5f7b2 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -181,6 +181,9 @@ module ActiveRecord
# * Stable, autogenerated IDs
# * Label references for associations (belongs_to, has_one, has_many)
# * HABTM associations as inline lists
+ #
+ # There are some more advanced features available even if the id is specified:
+ #
# * Autofilled timestamp columns
# * Fixture label interpolation
# * Support for YAML defaults
@@ -515,7 +518,7 @@ module ActiveRecord
::File.join(fixtures_directory, fs_name))
end
- all_loaded_fixtures.update(fixtures_map)
+ update_all_loaded_fixtures fixtures_map
connection.transaction(:requires_new => true) do
fixture_sets.each do |fs|
@@ -562,6 +565,10 @@ module ActiveRecord
@context_class ||= Class.new
end
+ def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
+ all_loaded_fixtures.update(fixtures_map)
+ end
+
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
@@ -654,7 +661,7 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- fk_type = association.active_record.columns_hash[association.foreign_key].type
+ fk_type = association.active_record.columns_hash[fk_name].type
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 15c9dee712..91747d2ccf 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveRecord
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index a9ddd9141f..45b6b1c925 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -37,7 +37,8 @@ module ActiveRecord
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
- column_types = result_set.column_types.except(*columns_hash.keys)
+ column_types = result_set.column_types.dup
+ columns_hash.each_key { |k| column_types.delete k }
result_set.map { |record| instantiate(record, column_types) }
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index c0deb76a33..6b5a592ee5 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -728,8 +728,11 @@ module ActiveRecord
through_scope_chain = through_reflection.scope_chain.map(&:dup)
if options[:source_type]
- through_scope_chain.first <<
- through_reflection.klass.where(foreign_type => options[:source_type])
+ type = foreign_type
+ source_type = options[:source_type]
+ through_scope_chain.first << lambda { |object|
+ where(type => source_type)
+ }
end
# Recursively fill out the rest of the array from the through reflection
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 90e99957f6..eaaa409636 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -19,15 +19,15 @@ module ActiveRecord
#
# Person.group(:city).count
# # => { 'Rome' => 5, 'Paris' => 3 }
- #
- # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
- # keys are an array containing the individual values of each column and the value
+ #
+ # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
+ # keys are an array containing the individual values of each column and the value
# of each key would be the +count+.
- #
+ #
# Article.group(:status, :category).count
- # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
# ["published", "business"]=>0, ["published", "technology"]=>2}
- #
+ #
# If +count+ is used with +select+, it will count the selected columns:
#
# Person.select(:age).count
@@ -274,7 +274,7 @@ module ActiveRecord
group_attrs = group_values
if group_attrs.first.respond_to?(:to_sym)
- association = @klass._reflect_on_association(group_attrs.first.to_sym)
+ association = @klass._reflect_on_association(group_attrs.first)
associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attrs)
else
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index a7899da3a8..ed56369f86 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -103,7 +103,7 @@ module ActiveRecord
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>take!</tt> accepts no arguments.
def take!
- take or raise RecordNotFound
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the first record (or first N records if a parameter is supplied).
@@ -138,7 +138,7 @@ module ActiveRecord
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>first!</tt> accepts no arguments.
def first!
- first or raise RecordNotFound
+ first or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the last record (or last N records if a parameter is supplied).
@@ -171,7 +171,7 @@ module ActiveRecord
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>last!</tt> accepts no arguments.
def last!
- last or raise RecordNotFound
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the second record.
@@ -187,7 +187,7 @@ module ActiveRecord
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def second!
- second or raise RecordNotFound
+ second or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the third record.
@@ -203,7 +203,7 @@ module ActiveRecord
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def third!
- third or raise RecordNotFound
+ third or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the fourth record.
@@ -219,7 +219,7 @@ module ActiveRecord
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def fourth!
- fourth or raise RecordNotFound
+ fourth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the fifth record.
@@ -235,7 +235,7 @@ module ActiveRecord
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def fifth!
- fifth or raise RecordNotFound
+ fifth or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Find the forty-second record. Also known as accessing "the reddit".
@@ -251,7 +251,7 @@ module ActiveRecord
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found.
def forty_two!
- forty_two or raise RecordNotFound
+ forty_two or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
end
# Returns +true+ if a record exists in the table that matches the +id+ or
@@ -401,8 +401,9 @@ module ActiveRecord
"#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
relation = relation.except(:select).select(values).distinct!
+ arel = relation.arel
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
id_rows.map {|row| row[primary_key]}
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index eff5c8f09c..3df0df40ee 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -26,7 +26,7 @@ module ActiveRecord
queries << '1=0'
else
table = Arel::Table.new(column, default_table.engine)
- association = klass._reflect_on_association(column.to_sym)
+ association = klass._reflect_on_association(column)
value.each do |k, v|
queries.concat expand(association && association.klass, table, k, v)
@@ -55,7 +55,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass._reflect_on_association(column.to_sym)
+ if klass && reflection = klass._reflect_on_association(column)
if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
queries << build(table[reflection.foreign_type], base_class)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 78dba8be06..b8d9240bf8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -2,12 +2,20 @@ module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
def call(attribute, value)
- return attribute.in([]) if value.empty?
-
values = value.map { |x| x.is_a?(Base) ? x.id : x }
- ranges, values = values.partition { |v| v.is_a?(Range) }
nils, values = values.partition(&:nil?)
+ if values.any? { |val| val.is_a?(Array) }
+ ActiveSupport::Deprecation.warn "Passing a nested array to Active Record " \
+ "finder methods is deprecated and will be removed. Flatten your array " \
+ "before using it for 'IN' conditions."
+ values = values.flatten
+ end
+
+ return attribute.in([]) if values.empty? && nils.empty?
+
+ ranges, values = values.partition { |v| v.is_a?(Range) }
+
values_predicate =
case values.length
when 0 then NullPredicate
@@ -20,7 +28,7 @@ module ActiveRecord
end
array_predicates = ranges.map { |range| attribute.in(range) }
- array_predicates << values_predicate
+ array_predicates.unshift(values_predicate)
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index e59cce6934..bbddd28ccc 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -687,11 +687,11 @@ module ActiveRecord
# end
#
def none
- extending(NullRelation)
+ where("1=0").extending!(NullRelation)
end
def none! # :nodoc:
- extending!(NullRelation)
+ where!("1=0").extending!(NullRelation)
end
# Sets readonly attributes for the returned relation. If value is
@@ -879,12 +879,10 @@ module ActiveRecord
arel.lock(lock_value) if lock_value
# Reorder bind indexes if joins produced bind values
- if arel.bind_values.any?
- bvs = arel.bind_values + bind_values
- arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
- column = bvs[i].first
- bp.replace connection.substitute_at(column, i)
- end
+ bvs = arel.bind_values + bind_values
+ arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
+ column = bvs[i].first
+ bp.replace connection.substitute_at(column, i)
end
arel
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index fae6427ea1..82c5ca291c 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -100,7 +100,7 @@ HEADER
# dump foreign keys at the end to make sure all dependent tables exist.
if @connection.supports_foreign_keys?
sorted_tables.each do |tbl|
- foreign_keys(tbl, stream)
+ foreign_keys(tbl, stream) unless ignored?(tbl)
end
end
end
@@ -111,12 +111,7 @@ HEADER
tbl = StringIO.new
# first dump primary key column
- if @connection.respond_to?(:pk_and_sequence_for)
- pk, _ = @connection.pk_and_sequence_for(table)
- end
- if !pk && @connection.respond_to?(:primary_key)
- pk = @connection.primary_key(table)
- end
+ pk = @connection.primary_key(table)
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
pkcol = columns.detect { |c| c.name == pk }
@@ -188,25 +183,22 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
+ "add_index #{remove_prefix_and_suffix(index.table).inspect}",
index.columns.inspect,
- ('name: ' + index.name.inspect),
+ "name: #{index.name.inspect}",
]
statement_parts << 'unique: true' if index.unique
index_lengths = (index.lengths || []).compact
- statement_parts << ('length: ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
-
- index_orders = (index.orders || {})
- statement_parts << ('order: ' + index.orders.inspect) unless index_orders.empty?
+ statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
- statement_parts << ('where: ' + index.where.inspect) if index.where
+ index_orders = index.orders || {}
+ statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
+ statement_parts << "where: #{index.where.inspect}" if index.where
+ statement_parts << "using: #{index.using.inspect}" if index.using
+ statement_parts << "type: #{index.type.inspect}" if index.type
- statement_parts << ('using: ' + index.using.inspect) if index.using
-
- statement_parts << ('type: ' + index.type.inspect) if index.type
-
- ' ' + statement_parts.join(', ')
+ " #{statement_parts.join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
@@ -218,26 +210,26 @@ HEADER
if (foreign_keys = @connection.foreign_keys(table)).any?
add_foreign_key_statements = foreign_keys.map do |foreign_key|
parts = [
- 'add_foreign_key ' + remove_prefix_and_suffix(foreign_key.from_table).inspect,
- remove_prefix_and_suffix(foreign_key.to_table).inspect,
- ]
+ "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
+ remove_prefix_and_suffix(foreign_key.to_table).inspect,
+ ]
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
- parts << ('column: ' + foreign_key.column.inspect)
+ parts << "column: #{foreign_key.column.inspect}"
end
if foreign_key.custom_primary_key?
- parts << ('primary_key: ' + foreign_key.primary_key.inspect)
+ parts << "primary_key: #{foreign_key.primary_key.inspect}"
end
if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
- parts << ('name: ' + foreign_key.name.inspect)
+ parts << "name: #{foreign_key.name.inspect}"
end
- parts << ('on_update: ' + foreign_key.on_update.inspect) if foreign_key.on_update
- parts << ('on_delete: ' + foreign_key.on_delete.inspect) if foreign_key.on_delete
+ parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
+ parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
- ' ' + parts.join(', ')
+ " #{parts.join(', ')}"
end
stream.puts add_foreign_key_statements.sort.join("\n")
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index f1384e0bb2..e3d6c5957e 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,3 +1,4 @@
+require 'active_record/type/decorator'
require 'active_record/type/mutable'
require 'active_record/type/numeric'
require 'active_record/type/time_value'
diff --git a/activerecord/lib/active_record/type/decorator.rb b/activerecord/lib/active_record/type/decorator.rb
new file mode 100644
index 0000000000..9fce38ea44
--- /dev/null
+++ b/activerecord/lib/active_record/type/decorator.rb
@@ -0,0 +1,14 @@
+module ActiveRecord
+ module Type
+ module Decorator # :nodoc:
+ def init_with(coder)
+ @subtype = coder['subtype']
+ __setobj__(@subtype)
+ end
+
+ def encode_with(coder)
+ coder['subtype'] = __getobj__
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 5b512433b0..17004b3593 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Type
class Serialized < SimpleDelegator # :nodoc:
include Mutable
+ include Decorator
attr_reader :subtype, :coder
@@ -36,14 +37,13 @@ module ActiveRecord
end
def init_with(coder)
- @subtype = coder['subtype']
@coder = coder['coder']
- __setobj__(@subtype)
+ super
end
def encode_with(coder)
- coder['subtype'] = @subtype
coder['coder'] = @coder
+ super
end
private
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 0b2f59b0eb..62c5abbf41 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -4,6 +4,8 @@ require 'support/connection_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
include ConnectionHelper
+ fixtures :comments
+
def setup
super
@subscriber = SQLSubscriber.new
@@ -24,6 +26,17 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_truncate
+ rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
+ count = rows.first.values.first
+ assert_operator count, :>, 0
+
+ ActiveRecord::Base.connection.truncate("comments")
+ rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
+ count = rows.first.values.first
+ assert_equal 0, count
+ end
+
def test_no_automatic_reconnection_after_timeout
assert @connection.active?
@connection.update('set @@wait_timeout=1')
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 8df1b7d18c..ff553c3f1a 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -12,12 +12,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- unless @connection.extension_enabled?('hstore')
- @connection.enable_extension 'hstore'
- @connection.commit_db_transaction
- end
-
- @connection.reconnect!
+ enable_extension!('hstore', @connection)
@connection.transaction do
@connection.create_table('pg_arrays') do |t|
@@ -32,6 +27,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
teardown do
@connection.execute 'drop table if exists pg_arrays'
+ disable_extension!('hstore', @connection)
end
def test_column
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 2acb64f81c..cb024463c9 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -10,12 +10,7 @@ if ActiveRecord::Base.connection.supports_extensions?
def setup
@connection = ActiveRecord::Base.connection
- unless @connection.extension_enabled?('citext')
- @connection.enable_extension 'citext'
- @connection.commit_db_transaction
- end
-
- @connection.reconnect!
+ enable_extension!('citext', @connection)
@connection.create_table('citexts') do |t|
t.citext 'cival'
@@ -24,7 +19,7 @@ if ActiveRecord::Base.connection.supports_extensions?
teardown do
@connection.execute 'DROP TABLE IF EXISTS citexts;'
- @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;'
+ disable_extension!('citext', @connection)
end
def test_citext_enabled
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index d26cda46fa..7d179944d4 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -8,6 +8,8 @@ module ActiveRecord
class NonExistentTable < ActiveRecord::Base
end
+ fixtures :comments
+
def setup
super
@subscriber = SQLSubscriber.new
@@ -20,6 +22,14 @@ module ActiveRecord
super
end
+ def test_truncate
+ count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i
+ assert_operator count, :>, 0
+ ActiveRecord::Base.connection.truncate("comments")
+ count = ActiveRecord::Base.connection.execute("select count(*) from comments").first['count'].to_i
+ assert_equal 0, count
+ end
+
def test_encoding
assert_not_nil @connection.encoding
end
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 889e369bd6..2968109346 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -9,9 +9,7 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- unless @connection.extension_enabled?('ltree')
- @connection.enable_extension 'ltree'
- end
+ enable_extension!('ltree', @connection)
@connection.transaction do
@connection.create_table('ltrees') do |t|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 9e5fd17dc4..6f40b0d2de 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
class SchemaTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -426,3 +427,28 @@ class SchemaTest < ActiveRecord::TestCase
assert_equal this_index_name, this_index.name
end
end
+
+class SchemaForeignKeyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_dump_foreign_key_targeting_different_schema
+ @connection.create_schema "my_schema"
+ @connection.create_table "my_schema.trains" do |t|
+ t.string :name
+ end
+ @connection.create_table "wagons" do |t|
+ t.integer :train_id
+ end
+ @connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id"
+ output = dump_table_schema "wagons"
+ assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output
+ ensure
+ @connection.execute "DROP TABLE IF EXISTS wagons"
+ @connection.execute "DROP TABLE IF EXISTS my_schema.trains"
+ @connection.execute "DROP SCHEMA IF EXISTS my_schema"
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6e7920b814..ae5b409f99 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -118,7 +118,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
end
setup do
- enable_uuid_ossp!(connection)
+ enable_extension!('uuid-ossp', connection)
connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
t.string 'name'
@@ -144,6 +144,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
drop_table "pg_uuids"
drop_table 'pg_uuids_2'
connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();'
+ disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -189,7 +190,7 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
include PostgresqlUUIDHelper
setup do
- enable_uuid_ossp!(connection)
+ enable_extension!('uuid-ossp', connection)
connection.create_table('pg_uuids', id: false) do |t|
t.primary_key :id, :uuid, default: nil
@@ -199,6 +200,7 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
teardown do
drop_table "pg_uuids"
+ disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -226,7 +228,7 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
end
setup do
- enable_uuid_ossp!(connection)
+ enable_extension!('uuid-ossp', connection)
connection.transaction do
connection.create_table('pg_uuid_posts', id: :uuid) do |t|
@@ -240,10 +242,9 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
end
teardown do
- connection.transaction do
drop_table "pg_uuid_comments"
drop_table "pg_uuid_posts"
- end
+ disable_extension!('uuid-ossp', connection)
end
if ActiveRecord::Base.connection.supports_extensions?
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
index 47b7d38eda..8a8e1d3b17 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -1,67 +1,63 @@
require "cases/helper"
+require "cases/view_test"
-module ViewTestConcern
- extend ActiveSupport::Concern
+class UpdateableViewTest < ActiveRecord::TestCase
+ fixtures :books
- included do
- self.use_transactional_fixtures = false
- mattr_accessor :view_type
+ class PrintedBook < ActiveRecord::Base
+ self.primary_key = "id"
end
- SCHEMA_NAME = 'test_schema'
- TABLE_NAME = 'things'
- COLUMNS = [
- 'id integer',
- 'name character varying(50)',
- 'email character varying(50)',
- 'moment timestamp without time zone'
- ]
-
- class ThingView < ActiveRecord::Base
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute <<-SQL
+ CREATE VIEW printed_books
+ AS SELECT id, name, status, format FROM books WHERE format = 'paperback'
+ SQL
end
- def setup
- super
- ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things"
-
- @connection = ActiveRecord::Base.connection
- @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
- @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
+ teardown do
+ @connection.execute "DROP VIEW printed_books" if @connection.table_exists? "printed_books"
end
- def teardown
- super
- @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
+ def test_update_record
+ book = PrintedBook.first
+ book.name = "AWDwR"
+ book.save!
+ book.reload
+ assert_equal "AWDwR", book.name
end
- def test_table_exists
- name = ThingView.table_name
- assert @connection.table_exists?(name), "'#{name}' table should exist"
+ def test_insert_record
+ PrintedBook.create! name: "Rails in Action", status: 0, format: "paperback"
+
+ new_book = PrintedBook.last
+ assert_equal "Rails in Action", new_book.name
end
- def test_column_definitions
- assert_nothing_raised do
- assert_equal COLUMNS, columns(ThingView.table_name)
+ def test_update_record_to_fail_view_conditions
+ book = PrintedBook.first
+ book.format = "ebook"
+ book.save!
+
+ assert_raises ActiveRecord::RecordNotFound do
+ book.reload
end
end
+end
- private
- def columns(table_name)
- @connection.send(:column_definitions, table_name).map do |name, type, default|
- "#{name} #{type}" + (default ? " default #{default}" : '')
- end
- end
+if ActiveRecord::Base.connection.supports_materialized_views?
+class MaterializedViewTest < ActiveRecord::TestCase
+ include ViewBehavior
-end
+ private
+ def create_view(name, query)
+ @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}"
+ end
-class ViewTest < ActiveRecord::TestCase
- include ViewTestConcern
- self.view_type = 'view'
-end
+ def drop_view(name)
+ @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.table_exists? name
-if ActiveRecord::Base.connection.supports_materialized_views?
- class MaterializedViewTest < ActiveRecord::TestCase
- include ViewTestConcern
- self.view_type = 'materialized_view'
end
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 b2bf9480dd..8c1c22d3bf 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -297,7 +297,7 @@ module ActiveRecord
def test_tables_logs_name
sql = <<-SQL
SELECT name FROM sqlite_master
- WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
@conn.tables('hello')
@@ -316,7 +316,7 @@ module ActiveRecord
with_example_table do
sql = <<-SQL
SELECT name FROM sqlite_master
- WHERE type = 'table'
+ WHERE (type = 'table' OR type = 'view')
AND NOT name = 'sqlite_sequence' AND name = \"ex\"
SQL
assert_logged [[sql.squish, 'SCHEMA', []]] do
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 3f5858714a..b6333240a8 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -5,7 +5,9 @@ if ActiveRecord::Base.connection.supports_migrations?
class ActiveRecordSchemaTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- def setup
+ setup do
+ @original_verbose = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
@connection = ActiveRecord::Base.connection
ActiveRecord::SchemaMigration.drop_table
end
@@ -16,6 +18,7 @@ if ActiveRecord::Base.connection.supports_migrations?
@connection.drop_table :nep_schema_migrations rescue nil
@connection.drop_table :has_timestamps rescue nil
ActiveRecord::SchemaMigration.delete_all rescue nil
+ ActiveRecord::Migration.verbose = @original_verbose
end
def test_has_no_primary_key
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 21912fdf0f..b852bd3536 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1290,4 +1290,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
david = Author.where(id: "1").eager_load(:readonly_comments).first!
assert david.readonly_comments.first.readonly?
end
+
+ test "preloading a polymorphic association with references to the associated table" do
+ post = Post.includes(:tags).references(:tags).where('tags.name = ?', 'General').first
+ assert_equal posts(:welcome), post
+ end
+
+ test "eager-loading a polymorphic association with references to the associated table" do
+ post = Post.eager_load(:tags).where('tags.name = ?', 'General').first
+ assert_equal posts(:welcome), post
+ 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 1d28c8dac9..e34b993029 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -28,6 +28,7 @@ require 'models/college'
require 'models/student'
require 'models/pirate'
require 'models/ship'
+require 'models/tyre'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -1941,4 +1942,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], authors(:david).posts_with_signature.map(&:title)
end
+
+ test 'associations autosaves when object is already persited' do
+ bulb = Bulb.create!
+ tyre = Tyre.create!
+
+ car = Car.create! do |c|
+ c.bulbs << bulb
+ c.tyres << tyre
+ end
+
+ assert_equal 1, car.bulbs.count
+ assert_equal 1, car.tyres.count
+ end
end
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 d9659a72c5..cddf1a1f72 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1147,23 +1147,4 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club.members << member
assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count
end
-
- class ClubWithCallbacks < ActiveRecord::Base
- self.table_name = 'clubs'
- after_create :add_a_member
-
- has_many :memberships, inverse_of: :club, foreign_key: :club_id
- has_many :members, through: :memberships
-
- def add_a_member
- members << Member.last
- end
- end
-
- def test_has_many_with_callback_before_association
- Member.create!
- club = ClubWithCallbacks.create!
-
- assert_equal 1, club.reload.memberships.count
- end
end
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index 24a332bb4a..321fb6c8dd 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE parents") if @connection.table_exists? 'parents'
- @connection.execute("DROP TABLE children") if @connection.table_exists? 'children'
+ @connection.drop_table 'parents' if @connection.table_exists? 'parents'
+ @connection.drop_table 'children' if @connection.table_exists? 'children'
end
test "belongs_to associations are not required by default" do
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index f663b5490c..9b0cf4c18f 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -23,7 +23,7 @@ require 'models/interest'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
- :computers, :people, :readers
+ :computers, :people, :readers, :authors, :author_favorites
def test_eager_loading_should_not_change_count_of_children
liquid = Liquid.create(:name => 'salty')
@@ -35,6 +35,13 @@ class AssociationsTest < ActiveRecord::TestCase
assert_equal 1, liquids[0].molecules.length
end
+ def test_subselect
+ author = authors :david
+ favs = author.author_favorites
+ fav2 = author.author_favorites.where(:author => Author.where(id: author.id)).to_a
+ assert_equal favs, fav2
+ end
+
def test_clear_association_cache_stored
firm = Firm.find(1)
assert_kind_of Firm, firm
@@ -350,4 +357,18 @@ class GeneratedMethodsTest < ActiveRecord::TestCase
def test_model_method_overrides_association_method
assert_equal(comments(:greetings).body, posts(:welcome).first_comment)
end
+
+ module MyModule
+ def comments; :none end
+ end
+
+ class MyArticle < ActiveRecord::Base
+ self.table_name = "articles"
+ include MyModule
+ has_many :comments, inverse_of: false
+ end
+
+ def test_included_module_overwrites_association_methods
+ assert_equal :none, MyArticle.new.comments
+ end
end
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index 644876752e..53bd58e22e 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -28,7 +28,7 @@ module ActiveRecord
teardown do
return unless @connection
- @connection.execute 'DROP TABLE attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
+ @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
Model.attribute_type_decorations.clear
Model.reset_column_information
end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 4741ee8799..e38b32d7fc 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -13,6 +13,7 @@ module ActiveRecord
def self.superclass; Base; end
def self.base_class; self; end
def self.decorate_matching_attribute_types(*); end
+ def self.initialize_generated_modules; end
include ActiveRecord::AttributeMethods
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index b4917e727a..7ca7349528 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -681,6 +681,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_yaml_dumping_record_with_time_zone_aware_attribute
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = Topic.new(id: 1)
+ record.written_on = "Jan 01 00:00:00 2014"
+ assert_equal record, YAML.load(YAML.dump(record))
+ end
+ end
+
def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
@@ -726,13 +734,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end
- def test_bulk_update_raise_unknown_attribute_errro
+ def test_bulk_update_raise_unknown_attribute_error
error = assert_raises(ActiveRecord::UnknownAttributeError) {
@target.new(:hello => "world")
}
- assert @target, error.record
- assert "hello", error.attribute
- assert "unknown attribute: hello", error.message
+ assert_instance_of @target, error.record
+ assert_equal "hello", error.attribute
+ assert_equal "unknown attribute: hello", error.message
end
def test_methods_override_in_multi_level_subclass
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 025cdbeba9..b2a7d3956d 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -19,6 +19,9 @@ require 'models/treasure'
require 'models/eye'
require 'models/electron'
require 'models/molecule'
+require 'models/member'
+require 'models/member_detail'
+require 'models/organization'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_validation
@@ -1116,6 +1119,27 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
end
+class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false unless supports_savepoints?
+
+ def setup
+ super
+ organization = Organization.create
+ @member = Member.create
+ MemberDetail.create(organization: organization, member: @member)
+ end
+
+ def test_should_not_has_one_through_model
+ class << @member.organization
+ def save(*args)
+ super
+ raise 'Oh noes!'
+ end
+ end
+ assert_nothing_raised { @member.save }
+ end
+end
+
class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 77d9ae9b8e..f53c496ecd 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -96,6 +96,14 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
+ def test_connections_closed_if_exception_and_explicitly_not_test
+ @env['rack.test'] = false
+ app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(NotImplementedError) { explosive.call(@env) }
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
test "doesn't clear active connections when running in a test case" do
@env['rack.test'] = true
@management.call(@env)
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 3b2f0dfe07..346fcab6ea 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -194,7 +194,8 @@ class EnumTest < ActiveRecord::TestCase
:valid, # generates #valid?, which conflicts with an AR method
:save, # generates #save!, which conflicts with an AR method
:proposed, # same value as an existing enum
- :public, :private, :protected, # generates a method that conflict with ruby words
+ :public, :private, :protected, # some important methods on Module and Class
+ :name, :parent, :superclass
]
conflicts.each_with_index do |value, i|
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index befbec4e1b..ac570ecbe0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -262,7 +262,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_take_bang_missing
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.where("title = 'This title does not exist'").take!
end
end
@@ -282,7 +282,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_first_bang_missing
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.where("title = 'This title does not exist'").first!
end
end
@@ -296,7 +296,7 @@ class FinderTest < ActiveRecord::TestCase
def test_model_class_responds_to_first_bang
assert Topic.first!
Topic.delete_all
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.first!
end
end
@@ -318,7 +318,7 @@ class FinderTest < ActiveRecord::TestCase
def test_model_class_responds_to_second_bang
assert Topic.second!
Topic.delete_all
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.second!
end
end
@@ -340,7 +340,7 @@ class FinderTest < ActiveRecord::TestCase
def test_model_class_responds_to_third_bang
assert Topic.third!
Topic.delete_all
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.third!
end
end
@@ -362,7 +362,7 @@ class FinderTest < ActiveRecord::TestCase
def test_model_class_responds_to_fourth_bang
assert Topic.fourth!
Topic.delete_all
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.fourth!
end
end
@@ -384,7 +384,7 @@ class FinderTest < ActiveRecord::TestCase
def test_model_class_responds_to_fifth_bang
assert Topic.fifth!
Topic.delete_all
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.fifth!
end
end
@@ -396,14 +396,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_last_bang_missing
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.where("title = 'This title does not exist'").last!
end
end
def test_model_class_responds_to_last_bang
assert_equal topics(:fifth), Topic.last!
- assert_raises ActiveRecord::RecordNotFound do
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
Topic.delete_all
Topic.last!
end
@@ -521,6 +521,34 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort
end
+ def test_find_on_hash_conditions_with_array_of_ranges
+ assert_equal [1,2,6,7,8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
+ end
+
+ def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges
+ assert_deprecated do
+ assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1..2], 3, [5], 6..8, 9]).to_a.map(&:id).sort
+ end
+ end
+
+ def test_find_on_hash_conditions_with_array_of_integers_and_arrays
+ assert_deprecated do
+ assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1, 2], 3, 5, [6, [7], 8], 9]).to_a.map(&:id).sort
+ end
+ end
+
+ def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_nils
+ assert_deprecated do
+ assert_equal [1,3,4,5], Topic.where(parent_id: [[2..6], nil]).to_a.map(&:id).sort
+ end
+ end
+
+ def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_more_nils
+ assert_deprecated do
+ assert_equal [], Topic.where(parent_id: [[7..10, nil, [nil]], [nil]]).to_a.map(&:id).sort
+ end
+ end
+
def test_find_on_multiple_hash_conditions
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
@@ -774,7 +802,9 @@ class FinderTest < ActiveRecord::TestCase
def test_find_by_one_attribute_bang
assert_equal topics(:first), Topic.find_by_title!("The First Topic")
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
+ assert_raises_with_message(ActiveRecord::RecordNotFound, "Couldn't find Topic") do
+ Topic.find_by_title!("The First Topic!")
+ end
end
def test_find_by_on_attribute_that_is_a_reserved_word
@@ -1043,6 +1073,11 @@ class FinderTest < ActiveRecord::TestCase
assert_equal nil, Post.find_by("1 = 0")
end
+ test "find_by with associations" do
+ assert_equal authors(:david), Post.find_by(author: authors(:david)).author
+ assert_equal authors(:mary) , Post.find_by(author: authors(:mary) ).author
+ end
+
test "find_by doesn't have implicit ordering" do
assert_sql(/^((?!ORDER).)*$/) { Post.find_by(id: posts(:eager_other).id) }
end
@@ -1085,4 +1120,10 @@ class FinderTest < ActiveRecord::TestCase
end
end)
end
+
+ def assert_raises_with_message(exception_class, message, &block)
+ err = assert_raises(exception_class) { block.call }
+ assert_match message, err.message
+ end
+
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 385ec48005..1050047a43 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -675,7 +675,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
require 'models/uuid_parent'
require 'models/uuid_child'
- fixtures :uuid_parents, :uuid_children
+ fixtures :uuid_parents, :uuid_children
end
def test_identifies_strings
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 1a31c2ec4a..be635aeef9 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -119,15 +119,23 @@ def verify_default_timezone_config
end
end
-def enable_uuid_ossp!(connection)
+def enable_extension!(extension, connection)
return false unless connection.supports_extensions?
- return connection.reconnect! if connection.extension_enabled?('uuid-ossp')
+ return connection.reconnect! if connection.extension_enabled?(extension)
- connection.enable_extension 'uuid-ossp'
+ connection.enable_extension extension
connection.commit_db_transaction
connection.reconnect!
end
+def disable_extension!(extension, connection)
+ return false unless connection.supports_extensions?
+ return true unless connection.extension_enabled?(extension)
+
+ connection.disable_extension extension
+ connection.reconnect!
+end
+
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
@@ -212,4 +220,4 @@ require 'mocha/setup' # FIXME: stop using mocha
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 713999df84..5a4b1fb919 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -5,6 +5,7 @@ require 'models/job'
require 'models/reader'
require 'models/ship'
require 'models/legacy_thing'
+require 'models/personal_legacy_thing'
require 'models/reference'
require 'models/string_key_object'
require 'models/car'
@@ -311,32 +312,24 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
# See Lighthouse ticket #1966
def test_destroy_dependents
- # Establish dependent relationship between People and LegacyThing
- add_counter_column_to(Person, 'legacy_things_count')
- LegacyThing.connection.add_column LegacyThing.table_name, 'person_id', :integer
- LegacyThing.reset_column_information
- LegacyThing.class_eval do
- belongs_to :person, :counter_cache => true
- end
- Person.class_eval do
- has_many :legacy_things, :dependent => :destroy
- end
+ # Establish dependent relationship between Person and PersonalLegacyThing
+ add_counter_column_to(Person, 'personal_legacy_things_count')
+ PersonalLegacyThing.reset_column_information
# Make sure that counter incrementing doesn't cause problems
p1 = Person.new(:first_name => 'fjord')
p1.save!
- t = LegacyThing.new(:person => p1)
+ t = PersonalLegacyThing.new(:person => p1)
t.save!
p1.reload
- assert_equal 1, p1.legacy_things_count
+ assert_equal 1, p1.personal_legacy_things_count
assert p1.destroy
assert_equal true, p1.frozen?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
- assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
+ assert_raises(ActiveRecord::RecordNotFound) { PersonalLegacyThing.find(t.id) }
ensure
- remove_counter_column_from(Person, 'legacy_things_count')
- LegacyThing.connection.remove_column LegacyThing.table_name, 'person_id'
- LegacyThing.reset_column_information
+ remove_counter_column_from(Person, 'personal_legacy_things_count')
+ PersonalLegacyThing.reset_column_information
end
private
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index ba28d55e4c..406dd70c37 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -29,8 +29,8 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.execute "DROP TABLE astronauts" if @connection.table_exists? 'astronauts'
- @connection.execute "DROP TABLE rockets" if @connection.table_exists? 'rockets'
+ @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
+ @connection.drop_table "rockets" if @connection.table_exists? 'rockets'
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index ba39fb1dec..c8b3f75e10 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -78,11 +78,12 @@ module ActiveRecord
end
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- enable_uuid_ossp!(connection)
+ enable_extension!('uuid-ossp', connection)
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
ensure
+ disable_extension!('uuid-ossp', connection)
connection.drop_table :cats if connection.table_exists? :cats
connection.drop_table :felines if connection.table_exists? :felines
end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 0d537fbfe3..8f40a1890d 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -117,7 +117,7 @@ class RelationMergingTest < ActiveRecord::TestCase
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
- fixtures :posts, :authors
+ fixtures :posts, :authors, :developers
test "merging where relations" do
hello_by_bob = Post.where(body: "hello").joins(:author).
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 1da5c36e1c..4c94c2fd0d 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -18,6 +18,10 @@ module ActiveRecord
def attribute_alias?(name)
false
end
+
+ def sanitize_sql(sql)
+ sql
+ end
end
def relation
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 580ea98910..39c8afdd23 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -181,7 +181,9 @@ module ActiveRecord
end
def test_where_with_table_name_and_nested_empty_array
- assert_equal [], Post.where(:id => [[]]).to_a
+ assert_deprecated do
+ assert_equal [], Post.where(:id => [[]]).to_a
+ end
end
def test_where_with_empty_hash_and_no_foreign_key
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7163697a68..410b5ba5a3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -420,6 +420,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal nil, ac.engines.maximum(:id)
end
+ def test_null_relation_in_where_condition
+ assert_operator Comment.count, :>, 0 # precondition, make sure there are comments.
+ assert_equal 0, Comment.where(post_id: Post.none).to_a.size
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index d5584ad06c..93fb502410 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -10,12 +10,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def standard_dump
- @stream = StringIO.new
- old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, []
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, @stream)
- @stream.string
- ensure
- ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
+ @@standard_dump ||= perform_schema_dump
+ end
+
+ def perform_schema_dump
+ dump_all_table_schema []
end
def test_dump_schema_information_outputs_lexically_ordered_versions
@@ -31,8 +30,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_magic_comment
- output = standard_dump
- assert_match "# encoding: #{@stream.external_encoding.name}", output
+ assert_match "# encoding: #{Encoding.default_external.name}", standard_dump
end
def test_schema_dump
@@ -100,7 +98,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_limit_constraint_for_integer_columns
output = dump_all_table_schema([/^(?!integer_limits)/])
+ assert_match %r{c_int_without_limit}, output
+
if current_adapter?(:PostgreSQLAdapter)
+ assert_no_match %r{c_int_without_limit.*limit:}, output
+
assert_match %r{c_int_1.*limit: 2}, output
assert_match %r{c_int_2.*limit: 2}, output
@@ -111,6 +113,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*limit:}, output
elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ assert_match %r{c_int_without_limit.*limit: 4}, output
+
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
@@ -118,13 +122,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*:limit}, output
elsif current_adapter?(:SQLite3Adapter)
+ assert_no_match %r{c_int_without_limit.*limit:}, output
+
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
assert_match %r{c_int_4.*limit: 4}, output
end
- assert_match %r{c_int_without_limit.*}, output
- assert_no_match %r{c_int_without_limit.*limit:}, output
if current_adapter?(:SQLite3Adapter)
assert_match %r{c_int_5.*limit: 5}, output
@@ -203,9 +207,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- def test_schema_dump_should_not_add_default_value_for_mysql_text_field
+ def test_schema_dump_should_add_default_value_for_mysql_text_field
output = standard_dump
- assert_match %r{t.text\s+"body",\s+null: false$}, output
+ assert_match %r{t.text\s+"body",\s+limit: 65535,\s+null: false$}, output
end
def test_schema_dump_includes_length_for_mysql_binary_fields
@@ -217,13 +221,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output
- assert_match %r{t.binary\s+"normal_blob"$}, output
+ assert_match %r{t.binary\s+"normal_blob",\s+limit: 65535$}, output
assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output
- assert_match %r{t.binary\s+"long_blob",\s+limit: 2147483647$}, output
+ assert_match %r{t.binary\s+"long_blob",\s+limit: 4294967295$}, output
assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output
- assert_match %r{t.text\s+"normal_text"$}, output
+ assert_match %r{t.text\s+"normal_text",\s+limit: 65535$}, output
assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
- assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
+ assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output
end
def test_schema_dumps_index_type
@@ -249,12 +253,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
connection = ActiveRecord::Base.connection
connection.stubs(:extensions).returns(['hstore'])
- output = standard_dump
+ output = perform_schema_dump
assert_match "# These are extensions that must be enabled", output
assert_match %r{enable_extension "hstore"}, output
connection.stubs(:extensions).returns([])
- output = standard_dump
+ output = perform_schema_dump
assert_no_match "# These are extensions that must be enabled", output
assert_no_match %r{enable_extension}, output
end
@@ -354,7 +358,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
assert_not_nil(match, "goofy_string_id table not found")
assert_match %r(id: false), match[1], "no table id not preserved"
- assert_match %r{t.string[[:space:]]+"id",[[:space:]]+null: false$}, match[2], "non-primary key id column not preserved"
+ assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved"
end
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
@@ -367,6 +371,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
assert_match(/^\s+add_foreign_key "fk_test_has_fk"[^\n]+\n\s+add_foreign_key "lessons_students"/, output)
end
+
+ def test_do_not_dump_foreign_keys_for_ignored_tables
+ output = dump_table_schema "authors"
+ assert_equal ["authors"], output.scan(/^\s*add_foreign_key "([^"]+)".+$/).flatten
+ end
end
class CreateDogMigration < ActiveRecord::Migration
@@ -395,7 +404,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
migration = CreateDogMigration.new
migration.migrate(:up)
- output = standard_dump
+ output = perform_schema_dump
assert_no_match %r{create_table "foo_.+_bar"}, output
assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
@@ -433,7 +442,7 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
def test_schema_dump_defaults_with_universally_supported_types
output = dump_table_schema('defaults')
- assert_match %r{t\.string\s+"string_with_default",\s+default: "Hello!"}, output
+ assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output
assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output
assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output
assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 9a4d8c6740..a5c4404175 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -1,9 +1,10 @@
require 'cases/helper'
require 'models/post'
+require 'models/comment'
require 'models/developer'
class DefaultScopingTest < ActiveRecord::TestCase
- fixtures :developers, :posts
+ fixtures :developers, :posts, :comments
def test_default_scope
expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary }
@@ -378,6 +379,24 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
end
+ def test_default_scope_with_references_works_through_collection_association
+ post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.")
+ comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id)
+ assert_equal comment, post.comment_with_default_scope_references_associations.to_a.first
+ end
+
+ def test_default_scope_with_references_works_through_association
+ post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.")
+ comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id)
+ assert_equal comment, post.first_comment
+ end
+
+ def test_default_scope_with_references_works_with_find_by
+ post = PostWithCommentWithDefaultScopeReferencesAssociation.create!(title: "Hello World", body: "Here we go.")
+ comment = post.comment_with_default_scope_references_associations.create!(body: "Great post.", developer_id: Developer.first.id)
+ assert_equal comment, CommentWithDefaultScopeReferencesAssociation.find_by(id: comment.id)
+ end
+
unless in_memory_db?
def test_default_scope_is_threadsafe
threads = []
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 59ec2dd6a4..d3546bd471 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -291,9 +291,12 @@ class NamedScopingTest < ActiveRecord::TestCase
:relation, # private class method on AR::Base
:new, # redefined class method on AR::Base
:all, # a default scope
- :public,
+ :public, # some imporant methods on Module and Class
:protected,
- :private
+ :private,
+ :name,
+ :parent,
+ :superclass
]
non_conflicts = [
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 9cfe041de2..5cccf2dda5 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -492,6 +492,37 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.frozen?, 'not frozen'
end
+ # The behavior of killed threads having a status of "aborting" was changed
+ # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction
+ # and there's nothing we can do about it.
+ unless RUBY_VERSION.start_with? '1.9'
+ def test_rollback_when_thread_killed
+ queue = Queue.new
+ thread = Thread.new do
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+
+ queue.push nil
+ sleep
+
+ @second.save
+ end
+ end
+
+ queue.pop
+ thread.kill
+ thread.join
+
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
+
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
+ end
+ end
+
def test_restore_active_record_state_for_all_records_in_a_transaction
topic_without_callbacks = Class.new(ActiveRecord::Base) do
self.table_name = 'topics'
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
new file mode 100644
index 0000000000..3aed90ba36
--- /dev/null
+++ b/activerecord/test/cases/view_test.rb
@@ -0,0 +1,113 @@
+require "cases/helper"
+require "models/book"
+
+module ViewBehavior
+ extend ActiveSupport::Concern
+
+ included do
+ fixtures :books
+ end
+
+ class Ebook < ActiveRecord::Base
+ self.primary_key = "id"
+ end
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ create_view "ebooks", <<-SQL
+ SELECT id, name, status FROM books WHERE format = 'ebook'
+ SQL
+ end
+
+ def teardown
+ super
+ drop_view "ebooks"
+ end
+
+ def test_reading
+ books = Ebook.all
+ assert_equal [books(:rfr).id], books.map(&:id)
+ assert_equal ["Ruby for Rails"], books.map(&:name)
+ end
+
+ def test_table_exists
+ view_name = Ebook.table_name
+ assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ end
+
+ def test_column_definitions
+ assert_equal([["id", :integer],
+ ["name", :string],
+ ["status", :integer]], Ebook.columns.map { |c| [c.name, c.type] })
+ end
+
+ def test_attributes
+ assert_equal({"id" => 2, "name" => "Ruby for Rails", "status" => 0},
+ Ebook.first.attributes)
+ end
+
+ def test_does_not_assume_id_column_as_primary_key
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ebooks"
+ end
+ assert_nil model.primary_key
+ end
+end
+
+if ActiveRecord::Base.connection.supports_views?
+class ViewWithPrimaryKeyTest < ActiveRecord::TestCase
+ include ViewBehavior
+
+ private
+ def create_view(name, query)
+ @connection.execute "CREATE VIEW #{name} AS #{query}"
+ end
+
+ def drop_view(name)
+ @connection.execute "DROP VIEW #{name}" if @connection.table_exists? name
+ end
+end
+
+class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
+ fixtures :books
+
+ class Paperback < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute <<-SQL
+ CREATE VIEW paperbacks
+ AS SELECT name, status FROM books WHERE format = 'paperback'
+ SQL
+ end
+
+ teardown do
+ @connection.execute "DROP VIEW paperbacks" if @connection.table_exists? "paperbacks"
+ end
+
+ def test_reading
+ books = Paperback.all
+ assert_equal ["Agile Web Development with Rails"], books.map(&:name)
+ end
+
+ def test_table_exists
+ view_name = Paperback.table_name
+ assert @connection.table_exists?(view_name), "'#{view_name}' table should exist"
+ end
+
+ def test_column_definitions
+ assert_equal([["name", :string],
+ ["status", :integer]], Paperback.columns.map { |c| [c.name, c.type] })
+ end
+
+ def test_attributes
+ assert_equal({"name" => "Agile Web Development with Rails", "status" => 0},
+ Paperback.first.attributes)
+ end
+
+ def test_does_not_have_a_primary_key
+ assert_nil Paperback.primary_key
+ end
+end
+end
diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml
index 3656564f63..1a74563dc6 100644
--- a/activerecord/test/fixtures/developers.yml
+++ b/activerecord/test/fixtures/developers.yml
@@ -18,4 +18,4 @@ dev_<%= digit %>:
poor_jamis:
id: 11
name: Jamis
- salary: 9000 \ No newline at end of file
+ salary: 9000
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 7a88299d08..b38b17e90e 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -52,3 +52,8 @@ class CommentThatAutomaticallyAltersPostBody < Comment
comment.post.update_attributes(body: "Automatically altered")
end
end
+
+class CommentWithDefaultScopeReferencesAssociation < Comment
+ default_scope ->{ includes(:developer).order('developers.name').references(:developer) }
+ belongs_to :developer
+end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index e76e83f314..8c83de573f 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -19,7 +19,7 @@ class LiveParrot < Parrot
end
class DeadParrot < Parrot
- belongs_to :killer, :class_name => 'Pirate'
+ belongs_to :killer, :class_name => 'Pirate', foreign_key: :killer_id
end
class FunkyParrot < Parrot
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index c7e54e7b63..ad12f00d42 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -30,6 +30,8 @@ class Person < ActiveRecord::Base
has_many :agents_of_agents, :through => :agents, :source => :agents
belongs_to :number1_fan, :class_name => 'Person'
+ has_many :personal_legacy_things, :dependent => :destroy
+
has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
has_many :essays, primary_key: "first_name", foreign_key: "writer_id"
diff --git a/activerecord/test/models/personal_legacy_thing.rb b/activerecord/test/models/personal_legacy_thing.rb
new file mode 100644
index 0000000000..a7ee3a0bca
--- /dev/null
+++ b/activerecord/test/models/personal_legacy_thing.rb
@@ -0,0 +1,4 @@
+class PersonalLegacyThing < ActiveRecord::Base
+ self.locking_column = :version
+ belongs_to :person, :counter_cache => true
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 56a31011da..256b720c9a 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -218,3 +218,9 @@ class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
post.comments.load
end
end
+
+class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base
+ 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
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 0584df87c6..10de3d34cb 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -10,7 +10,7 @@ ActiveRecord::Schema.define do
#put adapter specific setup here
case adapter_name
when "PostgreSQL"
- enable_uuid_ossp!(ActiveRecord::Base.connection)
+ enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
create_table :uuid_parents, id: :uuid, force: true do |t|
t.string :name
end
@@ -546,6 +546,12 @@ ActiveRecord::Schema.define do
t.column :treasure_id, :integer
end
+ create_table :personal_legacy_things, force: true do |t|
+ t.integer :tps_report_number
+ t.integer :person_id
+ t.integer :version, null: false, default: 0
+ end
+
create_table :pets, primary_key: :pet_id, force: true do |t|
t.string :name
t.integer :owner_id, :integer
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 617baaa1a8..4204057737 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,59 @@
+* Delegation now works with ruby reserved words passed to `:to` option.
+
+ Fixes #16956.
+
+ *Agis Anastasopoulos*
+
+* Added method `#eql?` to `ActiveSupport::Duration`, in addition to `#==`.
+
+ Currently, the following returns `false`, contrary to expectation:
+
+ 1.minute.eql?(1.minute)
+
+ Adding method `#eql?` will make this behave like expected. Method `#eql?` is
+ just a bit stricter than `#==`, as it checks whether the argument is also a duration. Their
+ parts may be different though.
+
+ 1.minute.eql?(60.seconds) # => true
+ 1.minute.eql?(60) # => false
+
+ *Joost Lubach*
+
+* Time#change can now change nanoseconds (:nsec) as a higher-precision
+ alternative to microseconds (:usec).
+
+ *Agis Anastasooulos*
+
+* `MessageVerifier.new` raises an appropriate exception if the secret is `nil`.
+ This prevents `MessageVerifier#generate` from raising a cryptic error later on.
+
+ *Kostiantyn Kahanskyi*
+
+* Introduced new configuration option `active_support.test_order` for
+ specifying the order in which test cases are executed. This option currently defaults
+ to `:sorted` but will be changed to `:random` in Rails 5.0.
+
+ *Akira Matsuda*, *Godfrey Chan*
+
+* Fixed a bug in Inflector#underscore where acroynms in nested constant names
+ are incorrectly parsed as camelCase.
+
+ Fixes #8015.
+
+ *Fred Wu*, *Matthew Draper*
+
+* Make Time#change throw an exception if the :usec option is out of range and
+ the time has an offset other than UTC or local.
+
+ *Agis Anastasopoulos*
+
+* `Method` objects now report themselves as not `duplicable?`. This allows
+ hashes and arrays containing `Method` objects to be `deep_dup`ed.
+
+ *Peter Jaros*
+
* `determine_constant_from_test_name` does no longer shadow `NameError`s
- which happen during constant autoloading.
+ which happens during constant autoloading.
Fixes #9933.
@@ -26,12 +80,6 @@
*DHH*
-* Fix ActiveSupport::TestCase not to order users' test cases by default.
- If this change breaks your tests because your tests are order dependent, you need to explicitly call
- ActiveSupport::TestCase.my_tests_are_order_dependent! at the top of your tests.
-
- *Akira Matsuda*
-
* Fix DateTime comparison with DateTime::Infinity object.
*Rafael Mendonça França*
@@ -83,8 +131,8 @@
* Always instrument `ActiveSupport::Cache`.
- Since `ActiveSupport::Notifications` only instrument items when there
- are subscriber we don't need to disable instrumentation.
+ Since `ActiveSupport::Notifications` only instruments items when there
+ are attached subscribers, we don't need to disable instrumentation.
*Peter Wagenet*
@@ -107,17 +155,17 @@
`ActiveSupport::TimeWithZone#-` should return the same result as if we were
using `Time#-`:
- Time.now.end_of_day - Time.now.beginning_of_day #=> 86399.999999999
+ Time.now.end_of_day - Time.now.beginning_of_day # => 86399.999999999
Before:
- Time.zone.now.end_of_day.nsec #=> 999999999
- Time.zone.now.end_of_day - Time.zone.now.beginning_of_day #=> 86400.0
+ Time.zone.now.end_of_day.nsec # => 999999999
+ Time.zone.now.end_of_day - Time.zone.now.beginning_of_day # => 86400.0
After:
Time.zone.now.end_of_day - Time.zone.now.beginning_of_day
- #=> 86399.999999999
+ # => 86399.999999999
*Gordon Chan*
@@ -126,12 +174,12 @@
Before:
ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- #=> "330.00"
+ # => "330.00"
After:
ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- #=> "333.33"
+ # => "333.33"
See #15379.
@@ -213,9 +261,9 @@
*Xavier Noria*
-* Fixed backward compatibility isues introduced in 326e652.
+* Fixed backward compatibility issues introduced in 326e652.
- Empty Hash or Array should not present in serialization result.
+ Empty Hash or Array should not be present in serialization result.
{a: []}.to_query # => ""
{a: {}}.to_query # => ""
@@ -234,20 +282,20 @@
This fixes the current situation of:
- 1.second.eql?(1.second) #=> false
+ 1.second.eql?(1.second) # => false
`eql?` also requires that the other object is an `ActiveSupport::Duration`.
This requirement makes `ActiveSupport::Duration`'s behavior consistent with
the behavior of Ruby's numeric types:
- 1.eql?(1.0) #=> false
- 1.0.eql?(1) #=> false
+ 1.eql?(1.0) # => false
+ 1.0.eql?(1) # => false
- 1.second.eql?(1) #=> false (was true)
- 1.eql?(1.second) #=> false
+ 1.second.eql?(1) # => false (was true)
+ 1.eql?(1.second) # => false
{ 1 => "foo", 1.0 => "bar" }
- #=> { 1 => "foo", 1.0 => "bar" }
+ # => { 1 => "foo", 1.0 => "bar" }
{ 1 => "foo", 1.second => "bar" }
# now => { 1 => "foo", 1.second => "bar" }
@@ -255,11 +303,11 @@
And though the behavior of these hasn't changed, for reference:
- 1 == 1.0 #=> true
- 1.0 == 1 #=> true
+ 1 == 1.0 # => true
+ 1.0 == 1 # => true
- 1 == 1.second #=> true
- 1.second == 1 #=> true
+ 1 == 1.second # => true
+ 1.second == 1 # => true
*Emily Dobervich*
@@ -269,8 +317,8 @@
*Pavel Pravosud*
-* `HashWithIndifferentAccess` better respects `#to_hash` on objects it's
- given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept
+* `HashWithIndifferentAccess` better respects `#to_hash` on objects it
+ recieves. In particular, `.new`, `#update`, `#merge`, `#replace` all accept
objects which respond to `#to_hash`, even if those objects are not Hashes
directly.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index ab0054b339..94468240a4 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -70,6 +70,16 @@ module ActiveSupport
NumberHelper.eager_load!
end
+
+ @@test_order = nil
+
+ def self.test_order=(new_order)
+ @@test_order = new_order
+ end
+
+ def self.test_order
+ @@test_order
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 9d5cee54e3..342d3a9d52 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -95,7 +95,7 @@ module ActiveSupport
# end
#
# class Host
- # include Bar # works, Bar takes care now of its dependencies
+ # include Bar # It works, now Bar takes care of its dependencies
# end
module Concern
class MultipleIncludedBlocks < StandardError #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index e926392952..570585b89a 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,8 +1,16 @@
+require 'set'
+
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
# option is not used.
class DelegationError < NoMethodError; end
+ RUBY_RESERVED_WORDS = Set.new(
+ %w(alias and BEGIN begin break case class def defined? do else elsif END
+ end ensure false for if in module next nil not or redo rescue retry
+ return self super then true undef unless until when while yield)
+ ).freeze
+
# Provides a +delegate+ class method to easily expose contained objects'
# public methods as your own.
#
@@ -163,7 +171,7 @@ class Module
line = line.to_i
to = to.to_s
- to = 'self.class' if to == 'class'
+ to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index c5d59128e5..665cb0f96d 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -91,3 +91,13 @@ class BigDecimal
# can't dup, so use superclass implementation
end
end
+
+class Method
+ # Methods are not duplicable:
+ #
+ # method(:puts).duplicable? # => false
+ # method(:puts).dup # => TypeError: allocator undefined for Method
+ def duplicable?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 1dfaf76673..2b1583d4ac 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -3,7 +3,7 @@ class String
# the string, and then changing remaining consecutive whitespace
# groups into one space each.
#
- # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E).
+ # Note that it handles both ASCII and Unicode whitespace.
#
# %{ Multi-line
# string }.squish # => "Multi-line string"
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index a943752f17..38d567c014 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -199,6 +199,7 @@ class String
# 'employee_salary'.humanize # => "Employee salary"
# 'author_id'.humanize # => "Author"
# 'author_id'.humanize(capitalize: false) # => "author"
+ # '_id'.humanize # => "Id"
def humanize(options = {})
ActiveSupport::Inflector.humanize(self, options)
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 89cd7516cd..ab8307429a 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -64,11 +64,12 @@ class Time
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
- # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
- # then minute, sec, and usec is set to 0. If the hour and minute is passed, then
- # sec and usec is set to 0. The +options+ parameter takes a hash with any of these
- # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>,
- # <tt>:sec</tt>, <tt>:usec</tt>.
+ # <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
+ # the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
+ # and minute is passed, then sec, usec and nsec is set to 0. The +options+
+ # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
+ # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
+ # <tt>:nsec</tt>. Path either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
@@ -80,13 +81,20 @@ class Time
new_hour = options.fetch(:hour, hour)
new_min = options.fetch(:min, options[:hour] ? 0 : min)
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
- new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+
+ if new_nsec = options[:nsec]
+ raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
+ new_usec = Rational(new_nsec, 1000)
+ else
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ end
if utc?
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
+ raise ArgumentError, 'argument out of range' if new_usec > 999999
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 1a6c02a39b..e861a17426 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -7,7 +7,7 @@ module ActiveSupport
# Time#advance, respectively. It mainly supports the methods on Numeric.
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
- class Duration < ProxyObject
+ class Duration
attr_accessor :value, :parts
def initialize(value, parts) #:nodoc:
@@ -53,8 +53,14 @@ module ActiveSupport
end
end
+ def to_s
+ @value.to_s
+ end
+
+ # Returns +true+ if +other+ is also a Duration instance, which has the
+ # same parts as this one.
def eql?(other)
- other.is_a?(Duration) && self == other
+ Duration === other && other.value.eql?(value)
end
def self.===(other) #:nodoc:
@@ -89,6 +95,10 @@ module ActiveSupport
to_i
end
+ def respond_to_missing?(method, include_private=false) #:nodoc
+ @value.respond_to?(method, include_private)
+ end
+
protected
def sum(sign, time = ::Time.current) #:nodoc:
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 0a2b4c1618..6233c45787 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveSupport
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 53022de549..f35e71ce81 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -91,7 +91,7 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::', '/')
- word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
+ word.gsub!(/(?:([A-Za-z\d])|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 8e6e1dcfeb..6cb2884fb7 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -27,6 +27,7 @@ module ActiveSupport
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
+ raise ArgumentError, 'Secret should not be nil.' unless secret
@secret = secret
@digest = options[:digest] || 'SHA1'
@serializer = options[:serializer] || Marshal
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 89009d1f55..7d45961515 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -42,7 +42,6 @@ module ActiveSupport
0x0085, # White_Space # Cc <control-0085>
0x00A0, # White_Space # Zs NO-BREAK SPACE
0x1680, # White_Space # Zs OGHAM SPACE MARK
- 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR
(0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
0x2028, # White_Space # Zl LINE SEPARATOR
0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 0df599b692..4c3e77b7fd 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -16,6 +16,35 @@ module ActiveSupport
Assertion = Minitest::Assertion
class << self
+ def test_order=(new_order)
+ ActiveSupport.test_order = new_order
+ end
+
+ def test_order
+ test_order = ActiveSupport.test_order
+
+ if test_order.nil?
+ ActiveSupport::Deprecation.warn "You did not specify a value for the " \
+ "configuration option 'active_support.test_order'. In Rails 5.0, " \
+ "the default value of this option will change from `:sorted` to " \
+ "`:random`.\n" \
+ "To disable this warning and keep the current behavior, you can add " \
+ "the following line to your `config/environments/test.rb`:\n" \
+ "\n" \
+ " Rails.application.configure do\n" \
+ " config.active_support.test_order = :sorted\n" \
+ " end\n" \
+ "\n" \
+ "Alternatively, you can opt into the future behavior by setting this " \
+ "option to `:random`."
+
+ test_order = :sorted
+ self.test_order = test_order
+ end
+
+ test_order
+ end
+
alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 52fbaf8a85..f65ec962f9 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -42,4 +42,4 @@ require 'mocha/setup' # FIXME: stop using mocha
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
require 'active_support/test_case'
-ActiveSupport::TestCase.my_tests_are_order_dependent!
+ActiveSupport::TestCase.test_order = :sorted
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index a89202f518..f4d504914e 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -40,14 +40,22 @@ class DurationTest < ActiveSupport::TestCase
assert !(1.day == 'foo')
end
+ def test_to_s
+ assert_equal "1", 1.second.to_s
+ end
+
def test_eql
rubinius_skip "Rubinius' #eql? definition relies on #instance_of? " \
"which behaves oddly for the sake of backward-compatibility."
assert 1.minute.eql?(1.minute)
+ assert 1.minute.eql?(60.seconds)
assert 2.days.eql?(48.hours)
assert !1.second.eql?(1)
assert !1.eql?(1.second)
+ assert 1.minute.eql?(180.seconds - 2.minutes)
+ assert !1.minute.eql?(60)
+ assert !1.minute.eql?('foo')
end
def test_inspect
@@ -90,8 +98,8 @@ class DurationTest < ActiveSupport::TestCase
def test_since_and_ago
t = Time.local(2000)
- assert t + 1, 1.second.since(t)
- assert t - 1, 1.second.ago(t)
+ assert_equal t + 1, 1.second.since(t)
+ assert_equal t - 1, 1.second.ago(t)
end
def test_since_and_ago_without_argument
@@ -183,4 +191,9 @@ class DurationTest < ActiveSupport::TestCase
cased = case 1.day when 1.day then "ok" end
assert_equal cased, "ok"
end
+
+ def test_respond_to
+ assert_respond_to 1.day, :since
+ assert_respond_to 1.day, :zero?
+ end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 380f5ad42b..3c49c4d14f 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -56,8 +56,14 @@ Developer = Struct.new(:client) do
delegate :name, :to => :client, :prefix => nil
end
+Event = Struct.new(:case) do
+ delegate :foo, :to => :case
+end
+
Tester = Struct.new(:client) do
delegate :name, :to => :client, :prefix => false
+
+ def foo; 1; end
end
Product = Struct.new(:name) do
@@ -495,4 +501,9 @@ class MethodAliasingTest < ActiveSupport::TestCase
assert_equal 'duck_with_orange', @instance.duck
assert FooClassWithBarMethod.public_method_defined?(:duck)
end
+
+ def test_delegate_with_case
+ event = Event.new(Tester.new)
+ assert_equal 1, event.foo
+ end
end
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index 84512380cf..8cc39ae7b9 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
- RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
+ RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)]
ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
# Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 18b4727c3b..2f4691817f 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -189,10 +189,10 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_string_squish
- original = %{\u180E\u180E A string surrounded by unicode mongolian vowel separators,
- with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u180E\u180E}
+ original = %{\u205f\u3000 A string surrounded by various unicode spaces,
+ with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007}
- expected = "A string surrounded by unicode mongolian vowel separators, " +
+ expected = "A string surrounded by various unicode spaces, " +
"with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."
# Make sure squish returns what we expect:
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index c8283cddc5..d59775001b 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -387,6 +387,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:min => 6)
assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(:sec => 7)
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8)
+ assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000)
+ assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) }
end
def test_utc_change
@@ -396,6 +398,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(:hour => 16)
assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(:hour => 16, :min => 45)
assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45)
+ assert_equal Time.utc(2005,1,2,11,22,33,8), Time.utc(2005,1,2,11,22,33,2).change(:nsec => 8000)
end
def test_offset_change
@@ -405,6 +408,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16)
assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45)
assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45)
+ assert_equal Time.new(2005,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,0,"-08:00").change(:sec => 10)
+ assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:usec => 10).usec
+ assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(:nsec => 10).nsec
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:usec => 1000000) }
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(:nsec => 1000000000) }
end
def test_advance
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 58fdea0972..b37f31bc5f 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -120,6 +120,7 @@ class InflectorTest < ActiveSupport::TestCase
["SSLError", "ssl_error", "SSL error", "SSL Error"],
["RESTful", "restful", "RESTful", "RESTful"],
["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"],
+ ["Nested::RESTful", "nested/restful", "Nested/RESTful", "Nested/RESTful"],
["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"],
["PhDRequired", "phd_required", "PhD required", "PhD Required"],
["IRoRU", "i_ror_u", "I RoR u", "I RoR U"],
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index b556da0046..3770f00fe3 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -141,6 +141,7 @@ module InflectorTestCases
"HTMLTidyGenerator" => "html_tidy_generator",
"FreeBSD" => "free_bsd",
"HTML" => "html",
+ "ForceXMLController" => "force_xml_controller",
}
CamelWithModuleToUnderscoreWithSlash = {
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index a5748d28ba..28035bc428 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -69,6 +69,13 @@ class MessageVerifierTest < ActiveSupport::TestCase
"undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
end
+ def test_raise_error_when_secret_is_nil
+ exception = assert_raise(ArgumentError) do
+ ActiveSupport::MessageVerifier.new(nil)
+ end
+ assert_equal exception.message, 'Secret should not be nil.'
+ end
+
def assert_not_verified(message)
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify(message)
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index c93192f207..5e852c8050 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -172,3 +172,50 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
assert_match "#{self.class}: #{name}\n", @out.string
end
end
+
+class TestOrderTest < ActiveSupport::TestCase
+ def setup
+ @original_test_order = ActiveSupport::TestCase.test_order
+ end
+
+ def teardown
+ ActiveSupport::TestCase.test_order = @original_test_order
+ end
+
+ def test_defaults_to_sorted_with_warning
+ ActiveSupport::TestCase.test_order = nil
+
+ assert_equal :sorted, assert_deprecated { ActiveSupport::TestCase.test_order }
+
+ # It should only produce a deprecation warning the first time this is accessed
+ assert_equal :sorted, assert_not_deprecated { ActiveSupport::TestCase.test_order }
+ assert_equal :sorted, assert_not_deprecated { ActiveSupport.test_order }
+ end
+
+ def test_test_order_is_global
+ ActiveSupport::TestCase.test_order = :random
+
+ assert_equal :random, ActiveSupport.test_order
+ assert_equal :random, ActiveSupport::TestCase.test_order
+ assert_equal :random, self.class.test_order
+ assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
+
+ ActiveSupport.test_order = :sorted
+
+ assert_equal :sorted, ActiveSupport.test_order
+ assert_equal :sorted, ActiveSupport::TestCase.test_order
+ assert_equal :sorted, self.class.test_order
+ assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order
+ end
+
+ def test_i_suck_and_my_tests_are_order_dependent!
+ ActiveSupport::TestCase.test_order = :random
+
+ klass = Class.new(ActiveSupport::TestCase) do
+ i_suck_and_my_tests_are_order_dependent!
+ end
+
+ assert_equal :alpha, klass.test_order
+ assert_equal :random, ActiveSupport::TestCase.test_order
+ end
+end
diff --git a/ci/travis.rb b/ci/travis.rb
index db6e41ecfa..a1920a7a1f 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -48,6 +48,7 @@ class Build
heading = [gem]
heading << "with #{adapter}" if activerecord?
heading << "in isolation" if isolated?
+ heading << "integration" if integration?
heading.join(' ')
end
@@ -55,7 +56,7 @@ class Build
if activerecord?
['db:mysql:rebuild', "#{adapter}:#{'isolated_' if isolated?}test"]
else
- ["test#{':isolated' if isolated?}"]
+ ["test", ('isolated' if isolated?), ('integration' if integration?)].compact.join(":")
end
end
@@ -74,6 +75,10 @@ class Build
options[:isolated]
end
+ def integration?
+ component.split(':').last == 'integration'
+ end
+
def gem
MAP[component.split(':').first]
end
@@ -93,11 +98,18 @@ class Build
end
end
+if ENV['GEM']=='aj:integration'
+ ENV['QC_DATABASE_URL'] = 'postgres://postgres@localhost/active_jobs_qc_int_test'
+ ENV['QUE_DATABASE_URL'] = 'postgres://postgres@localhost/active_jobs_que_int_test'
+end
+
results = {}
ENV['GEM'].split(',').each do |gem|
[false, true].each do |isolated|
+ next if ENV['TRAVIS_PULL_REQUEST'] && ENV['TRAVIS_PULL_REQUEST'] != 'false' && isolated
next if gem == 'railties' && isolated
+ next if gem == 'aj:integration' && isolated
build = Build.new(gem, :isolated => isolated)
results[build.key] = build.run!
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 8122d6c235..46be2613ab 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -545,7 +545,7 @@ These are the main changes in Active Support:
* `String#to_time` and `String#to_datetime` handle fractional seconds.
* Added support to new callbacks for around filter object that respond to `:before` and `:after` used in before and after callbacks.
* The `ActiveSupport::OrderedHash#to_a` method returns an ordered set of arrays. Matches Ruby 1.9's `Hash#to_a`.
-* `MissingSourceFile` exists as a constant but it is now just equals to `LoadError`.
+* `MissingSourceFile` exists as a constant but it is now just equal to `LoadError`.
* Added `Class#class_attribute`, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses.
* Finally removed `DeprecatedCallbacks` in `ActiveRecord::Associations`.
* `Object#metaclass` is now `Kernel#singleton_class` to match Ruby.
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index ae8ef34cdd..2b8a6eb622 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -25,9 +25,8 @@ If you're upgrading an existing application, it's a great idea to have good test
coverage before going in. You should also first upgrade to Rails 4.1 in case you
haven't and make sure your application still runs as expected before attempting
to upgrade to Rails 4.2. A list of things to watch out for when upgrading is
-available in the
-[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2)
-guide.
+available in the guide: [Upgrading Ruby on
+Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2)
Major Features
@@ -36,9 +35,12 @@ Major Features
### Active Job, Action Mailer #deliver_later
Active Job is a new framework in Rails 4.2. It is an adapter layer on top of
-queuing systems like [Resque](https://github.com/resque/resque), [Delayed Job](https://github.com/collectiveidea/delayed_job), [Sidekiq](https://github.com/mperham/sidekiq), and more. You can write your
-jobs with the Active Job API, and it'll run on all these queues with no changes
-(it comes pre-configured with an inline runner).
+queuing systems like [Resque](https://github.com/resque/resque), [Delayed
+Job](https://github.com/collectiveidea/delayed_job),
+[Sidekiq](https://github.com/mperham/sidekiq), and more.
+
+You can write your jobs with the Active Job API, and it'll run on all these
+queues with no changes (it comes pre-configured with an inline runner).
Building on top of Active Job, Action Mailer now comes with a `#deliver_later`
method, which adds your email to be sent as a job to a queue, so it doesn't
@@ -52,11 +54,45 @@ deserialize it at run time.
### Adequate Record
-Rails 4.2 comes with a performance improvement feature called Adequate Record
-for Active Record. A lot of common queries are now up to twice as fast in Rails
-4.2!
+Adequate Record is a set of refactorings that make Active Record `find` and
+`find_by` methods and some association queries upto 2x faster.
+
+It works by caching SQL query patterns while executing the Active Record calls.
+The cache helps skip parts of the computation involved in the transformation of
+the calls into SQL queries. More details in [Aaron Patterson's
+post](http://tenderlovemaking.com/2014/02/19/adequaterecord-pro-like-activerecord.html).
+
+Nothing special has to be done to activate this feature. Most `find` and
+`find_by` calls and association queries will use it automatically. Examples:
+
+```ruby
+Post.find 1 # caches query pattern
+Post.find 2 # uses the cached pattern
+
+Post.find_by_title 'first post' # caches query pattern
+Post.find_by_title 'second post' # uses the cached pattern
+
+post.comments # caches query pattern
+post.comments(true) # uses cached pattern
+```
+
+The caching is not used in the following scenarios:
+
+- The model has a default scope
+- The model uses single table inheritence to inherit from another model
+- `find` with a list of ids. eg:
+
+ ```ruby
+ Post.find(1,2,3)
+ OR
+ Post.find [1,2]
+ ```
-TODO: add some technical details
+- `find_by` with sql fragments:
+
+ ```ruby
+ Post.find_by "published_at < ?", 2.weeks.ago
+ ```
### Web Console
@@ -113,6 +149,13 @@ individual components for new deprecations in this release.
The following changes may require immediate action upon upgrade.
+### `render` with a String argument
+
+Previously, calling `render "foo/bar"` in a controller action is equivalent to
+`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean `render template: "foo/bar"`
+instead. If you need to render a file, please change your code to use the
+explicit form (`render file: "foo/bar"`) instead.
+
### `respond_with` / class-level `respond_to`
`respond_with` and the corresponding class-level `respond_to` have been moved to
@@ -148,6 +191,21 @@ class UsersController < ApplicationController
end
```
+### Default host for `rails server`
+
+Due to a [change in Rack](https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc),
+`rails server` now listens on `localhost` instead of `0.0.0.0` by default. This
+should have minimal impact on the standard development workflow as both http://127.0.0.1:3000
+and http://localhost:3000 would continue to work as before on your own machine.
+
+However, with this change you would no longer be able to access the Rails server
+from a different machine (e.g. your development environment is in a virtual
+machine and you would like to access it from the host machine), you would need
+to start the server with `rails server -b 0.0.0.0` to restore the old behavior.
+
+If you do this, be sure to configure your firewall properly such that only
+trusted machines on your network can access your development server.
+
### Production logging
The default log level in the `production` environment is now `:debug`. This
@@ -167,7 +225,8 @@ config.log_level = :info
### HTML Sanitizer
The HTML sanitizer has been replaced with a new, more robust, implementation
-built upon Loofah and Nokogiri. The new sanitizer is (TODO: betterer).
+built upon Loofah and Nokogiri. The new sanitizer is more secure and its
+sanitization is more powerful and flexible.
With a new sanitization algorithm, the sanitized output will change for certain
pathological inputs.
@@ -292,7 +351,7 @@ Please refer to the [Changelog][railties] for detailed changes.
namespace: my_app_development
# config/production.rb
- MyApp::Application.configure do
+ Rails.application.configure do
config.middleware.use ExceptionNotifier, config_for(:exception_notification)
end
```
@@ -447,6 +506,10 @@ Please refer to the [Changelog][action-view] for detailed changes.
### Notable changes
+* `render "foo/bar"` now expands to `render template: "foo/bar"` instead of
+ `render file: "foo/bar"`.
+ ([Pull Request](https://github.com/rails/rails/pull/16888))
+
* Introduced a `#{partial_name}_iteration` special local variable for use with
partials that are rendered with a collection. It provides access to the
current state of the iteration via the `#index`, `#size`, `#first?` and
@@ -645,7 +708,7 @@ Please refer to the [Changelog][active-model] for detailed changes.
(Pull Request [1](https://github.com/rails/rails/pull/14861),
[2](https://github.com/rails/rails/pull/16180))
-* `has_secure_password` no longer disallow blank passwords (i.e. passwords
+* `has_secure_password` no longer disallows blank passwords (i.e. passwords
that contains only spaces) by default.
([Pull Request](https://github.com/rails/rails/pull/16412))
@@ -687,13 +750,14 @@ Please refer to the [Changelog][active-support] for detailed changes.
### Notable changes
+* Introduced new configuration option `active_support.test_order` for
+ specifying the order test cases are executed. This option currently defaults
+ to `:sorted` but will be changed to `:random` in Rails 5.0.
+ ([Commit](TODO: fill me in))
+
* The `travel_to` test helper now truncates the `usec` component to 0.
([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5))
-* `ActiveSupport::TestCase` now randomizes the order that test cases are ran
- by default.
- ([Commit](https://github.com/rails/rails/commit/6ffb29d24e05abbd9ffe3ea974140d6c70221807))
-
* Introduced `Object#itself` as an identity function.
(Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810),
[2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a))
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 4c04a06dbb..ca1e79d3ce 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1183,9 +1183,9 @@ First define your app own routes to display the errors page.
* `config/routes.rb`
```ruby
- get '/404', to: 'errors#not_found'
- get '/422', to: 'errors#unprocessable_entity'
- get '/500', to: 'errors#server_error'
+ match '/404', via: :all, to: 'errors#not_found'
+ match '/422', via: :all, to: 'errors#unprocessable_entity'
+ match '/500', via: :all, to: 'errors#server_error'
```
Create the controller and views.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 2c6e79a5b0..9c34418fab 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -78,15 +78,15 @@ end
Enqueue a job like so:
```ruby
-MyJob.enqueue 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 the queueing system is free.
```
```ruby
-MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
+MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) # Enqueue a job to be performed tomorrow at noon.
```
```ruby
-MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
+MyJob.set(wait: 1.week).perform_later(record) # Enqueue a job to be performed 1 week from now.
```
That's it!
@@ -99,46 +99,18 @@ If no adapter is set, the job is immediately executed.
### Backends
-Active Job has adapters for the following queueing backends:
-
-* [Backburner](https://github.com/nesquena/backburner)
-* [Delayed Job](https://github.com/collectiveidea/delayed_job)
-* [Qu](https://github.com/bkeepers/qu)
-* [Que](https://github.com/chanks/que)
-* [QueueClassic 2.x](https://github.com/ryandotsmith/queue_classic/tree/v2.2.3)
-* [Resque 1.x](https://github.com/resque/resque/tree/1-x-stable)
-* [Sidekiq](https://github.com/mperham/sidekiq)
-* [Sneakers](https://github.com/jondot/sneakers)
-* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
-
-#### Backends Features
-
-| | Async | Queues | Delayed | Priorities | Timeout | Retries |
-|-----------------------|-------|--------|-----------|------------|---------|---------|
-| **Backburner** | Yes | Yes | Yes | Yes | Job | Global |
-| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global |
-| **Que** | Yes | Yes | Yes | Job | No | Job |
-| **Queue Classic** | Yes | Yes | No* | No | No | No |
-| **Resque** | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
-| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job |
-| **Sneakers** | Yes | Yes | No | Queue | Queue | No |
-| **Sucker Punch** | Yes | Yes | No | No | No | No |
-| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A |
-| **Active Job** | Yes | Yes | Yes | No | No | No |
-
-NOTE:
-* Queue Classic does not support Job scheduling. However you can implement this
-yourself or you can use the queue_classic-later gem. See the documentation for
-ActiveJob::QueueAdapters::QueueClassicAdapter.
-
-### Change Backends
-
-You can easily change your adapter:
+Active Job has built-in adapters for multiple queueing backends (Sidekiq,
+Resque, Delayed Job and others). To get an up-to-date list of the adapters
+see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
+
+### Changing the Backend
+
+You can easily change your queueing backend:
```ruby
# be sure to have the adapter gem in your Gemfile and follow the adapter specific
# installation and deployment instructions
-YourApp::Application.config.active_job.queue_adapter = :sidekiq
+Rails.application.config.active_job.queue_adapter = :sidekiq
```
@@ -155,7 +127,7 @@ class GuestsCleanupJob < ActiveJob::Base
end
```
-Also you can prefix the queue name for all your jobs using
+You can prefix the queue name for all your jobs using
`config.active_job.queue_name_prefix` in `application.rb`:
```ruby
@@ -172,10 +144,42 @@ class GuestsCleanupJob < ActiveJob::Base
#....
end
-# Now your job will run on queue production_low_priority on your production
-# environment and on beta_low_priority on your beta environment
+# Now your job will run on queue production_low_priority on your
+# production environment and on beta_low_priority on your beta
+# environment
+```
+
+If you want more control on what queue a job will be run you can pass a :queue
+option to #set:
+
+```ruby
+MyJob.set(queue: :another_queue).perform_later(record)
+```
+
+To control the queue from the job level you can pass a block to queue_as. The
+block will be executed in the job context (so you can access self.arguments)
+and you must return the queue name:
+
+```ruby
+class ProcessVideoJob < ActiveJob::Base
+ queue_as do
+ video = self.arguments.first
+ if video.owner.premium?
+ :premium_videojobs
+ else
+ :videojobs
+ end
+ end
+
+ def perform(video)
+ # do process video
+ end
+end
+
+ProcessVideoJob.perform_later(Video.last)
```
+
NOTE: Make sure your queueing backend "listens" on your queue name. For some
backends you need to specify the queues to listen to.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index cb243c95f5..e1a465c64f 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -340,16 +340,14 @@ The `find_in_batches` method is similar to `find_each`, since both retrieve batc
```ruby
# Give add_invoices an array of 1000 invoices at a time
-Invoice.find_in_batches(include: :invoice_lines) do |invoices|
+Invoice.find_in_batches do |invoices|
export.add_invoices(invoices)
end
```
-NOTE: The `:include` option allows you to name associations that should be loaded alongside with the models.
-
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`, as well as most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_in_batches`.
+The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`.
Conditions
----------
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 965de0c761..de42f13145 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -162,7 +162,7 @@ Active Support provides `duplicable?` to programmatically query an object about
false.duplicable? # => false
```
-By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects.
+By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, module, and method objects.
WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case.
@@ -1268,7 +1268,7 @@ The method `squish` strips leading and trailing whitespace, and substitutes runs
There's also the destructive version `String#squish!`.
-Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E).
+Note that it handles both ASCII and Unicode whitespace.
NOTE: Defined in `active_support/core_ext/string/filters.rb`.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 15d743fb33..c19c8e0bec 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -208,9 +208,7 @@ precompiling works.
NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript.
If you are using Mac OS X or Windows, you have a JavaScript runtime installed in
-your operating system. Check
-[ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all
-supported JavaScript runtimes.
+your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes.
You can also disable generation of controller specific asset files by adding the
following to your `config/application.rb` configuration:
@@ -737,10 +735,10 @@ Rails.application.config.assets.precompile << Proc.new do |path|
full_path = Rails.application.assets.resolve(path).to_path
app_assets_path = Rails.root.join('app', 'assets').to_path
if full_path.starts_with? app_assets_path
- puts "including asset: " + full_path
+ logger.info "including asset: " + full_path
true
else
- puts "excluding asset: " + full_path
+ logger.info "excluding asset: " + full_path
false
end
else
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index a074b849c6..b9014724bd 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -130,7 +130,7 @@ Example:
`rails generate controller CreditCards open debit credit close`
Credit card controller with URLs like /credit_cards/debit.
- Controller: app/controllers/credit_card_controller.rb
+ Controller: app/controllers/credit_cards_controller.rb
Test: test/controllers/credit_cards_controller_test.rb
Views: app/views/credit_cards/debit.html.erb [...]
Helper: app/helpers/credit_cards_helper.rb
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index dbbd0c1aea..7b9710bcd8 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -62,7 +62,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`.
-* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with `threadsafe!`.
+* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes.
* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
@@ -86,7 +86,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
end
```
-* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode. This flag is set to false by `config.threadsafe!`.
+* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode.
* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
@@ -108,7 +108,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
-* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`.
+* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments.
* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
@@ -364,6 +364,29 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
for more information. It defaults to true.
+* `config.action_dispatch.rescue_responses` configures what exceptions are assigned to an HTTP status. It accepts a hash and you can specify pairs of exception/status. By default, this is defined as:
+
+ ```ruby
+ config.action_dispatch.rescue_responses = {
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
+ 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request,
+ 'ActiveRecord::RecordNotFound' => :not_found,
+ 'ActiveRecord::StaleObjectError' => :conflict,
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity
+ }
+ ```
+
+ Any execptions that are not configured will be assigned to 500 Internal server error.
+
* `ActionDispatch::Callbacks.before` takes a block of code to run before the request.
* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`.
@@ -471,6 +494,8 @@ There are a few configuration options available in Active Support:
* `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded.
+* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead.
+
* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`.
* `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 8bc4b10591..302c4ca9c0 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -559,6 +559,23 @@ $ git push origin my_pull_request -f
You should be able to refresh the pull request on GitHub and see that it has
been updated.
+#### Updating pull request
+
+Sometimes you will be asked to make some changes to the code you have
+already committed. This can include amending existing commits. In this
+case Git will not allow you to push the changes as the pushed branch
+and local branch do not match. Instead of opening a new pull request,
+you can force push to your branch on GitHub as described earlier in
+squashing commits section:
+
+```bash
+$ git push origin my_pull_request -f
+```
+
+This will update the branch and pull request on GitHub with your new code. Do
+note that using force push may result in commits being lost on the remote branch; use it with care.
+
+
### Older Versions of Ruby on Rails
If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 4-0-stable branch:
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 88c6210296..1a647f8375 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -138,7 +138,7 @@ Rails.logger.level = 0 # at any time
This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information.
-TIP: The default Rails log level is `info` in production mode and `debug` in development and test mode.
+TIP: The default Rails log level is `debug` in all environments.
### Sending Messages
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 1023598aa4..0761d5b39c 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -676,6 +676,22 @@ en:
<div><%= t('title.html') %></div>
```
+Interpolation escapes as needed though. For example, given:
+
+```yaml
+en:
+ welcome_html: "<b>Welcome %{username}!</b>"
+```
+
+you can safely pass the username as set by the user:
+
+```erb
+<%# This is safe, it is going to be escaped if needed. %>
+<%= t('welcome_html', username: @current_user.username %>
+```
+
+Safe strings on the other hand are interpolated verbatim.
+
NOTE: Automatic conversion to HTML safe translate text is only available from the `translate` view helper method.
![i18n demo html safe](images/i18n/demo_html_safe.png)
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 6f8584b3b7..050a64ddf3 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -39,7 +39,10 @@ Only the latest release series will receive bug fixes. When enough bugs are
fixed and its deemed worthy to release a new gem, this is the branch it happens
from.
-**Currently included series:** `4.1.Z`, `4.0.Z`.
+In special situations, where someone from the Core Team agrees to support more series,
+they are included in the list of supported series.
+
+**Currently included series:** `4.2.Z`, `4.1.Z` (Supported by Rafael França).
Security Issues
---------------
@@ -54,7 +57,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** `4.1.Z`, `4.0.Z`.
+**Currently included series:** `4.2.Z`, `4.1.Z`.
Severe Security Issues
----------------------
@@ -63,7 +66,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** `4.1.Z`, `4.0.Z`, `3.2.Z`.
+**Currently included series:** `4.2.Z`, `4.1.Z`, `3.2.Z`.
Unsupported Release Series
--------------------------
diff --git a/guides/source/routing.md b/guides/source/routing.md
index af8c1bbcc4..b1a287f53a 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -756,7 +756,7 @@ get '*a/foo/*b', to: 'test#index'
would match `zoo/woo/foo/bar/baz` with `params[:a]` equals `'zoo/woo'`, and `params[:b]` equals `'bar/baz'`.
-NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equals to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this:
+NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equal to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this:
```ruby
get '*pages', to: 'pages#show', format: false
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 989d8400e5..7267ef1b4f 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -8,7 +8,7 @@ This guide provides steps to be followed when you upgrade your applications to a
General Advice
--------------
-Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance out several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few.
+Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few.
### Test Coverage
@@ -55,15 +55,88 @@ a [pull request](https://github.com/rails/rails/edit/master/guides/source/upgrad
### Web Console
-TODO: setup instructions for web console on existing apps.
+First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
+
+Additionally, you can tell Rails to automatically mount a VT100-compatible console on a predetermined path by setting the appropriate configuration flags in your development config:
+
+```ruby
+# config/environments/development.rb
+
+config.web_console.automount = true
+config.web_console.default_mount_path = '/terminal' # Optional, defaults to /console
+```
### Responders
-TODO: mention https://github.com/rails/rails/pull/16526
+`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
+
+```ruby
+# app/controllers/users_controller.rb
+
+class UsersController < ApplicationController
+ respond_to :html, :json
+
+ def show
+ @user = User.find(params[:id])
+ respond_with @user
+ end
+end
+```
+
+Instance-level `respond_to` is unaffected and does not require the additional gem:
+
+```ruby
+# app/controllers/users_controller.rb
+
+class UsersController < ApplicationController
+ def show
+ @user = User.find(params[:id])
+ respond_to do |format|
+ format.html
+ format.json { render json: @user }
+ end
+ end
+end
+```
+
+See [#16526](https://github.com/rails/rails/pull/16526) for more details.
### Error handling in transaction callbacks
-TODO: mention https://github.com/rails/rails/pull/16537
+Currently, Active Record suppresses errors raised
+within `after_rollback` or `after_commit` callbacks and only prints them to
+the logs. In the next version, these errors will no longer be suppressed.
+Instead, the errors will propagate normally just like in other Active
+Record callbacks.
+
+When you define a `after_rollback` or `after_commit` callback, you
+will receive a deprecation warning about this upcoming change. When
+you are ready, you can opt into the new behavior and remove the
+deprecation warning by adding following configuration to your
+`config/application.rb`:
+
+ config.active_record.raise_in_transactional_callbacks = true
+
+See [#14488](https://github.com/rails/rails/pull/14488) and
+[#16537](https://github.com/rails/rails/pull/16537) for more details.
+
+### Ordering of test cases
+
+In Rails 5.0, test cases will be executed in random order by default. In
+anticipation of this change, Rails 4.2 introduced a new configuration option
+`active_support.test_order` for explicitly specifying the test ordering. This
+allows you to either lock down the current behavior by setting the option to
+`:sorted`, or opt into the future behavior by setting the option to `:random`.
+
+If you do not specify a value for this option, a deprecation warning will be
+emitted. To avoid this, add the following line to your test environment:
+
+```ruby
+# config/environments/test.rb
+Rails.application.configure do
+ config.active_support.test_order = :sorted # or `:random` if you prefer
+end
+```
### Serialized attributes
@@ -113,15 +186,6 @@ venerable html-scanner approach is now officially being deprecated in favor of
This means the methods `sanitize`, `sanitize_css`, `strip_tags` and
`strip_links` are backed by a new implementation.
-In the next major Rails version `Rails Html Sanitizer` will be the default
-sanitizer. It already is for new applications.
-
-Include this in your Gemfile to try it out today:
-
-```ruby
-gem 'rails-html-sanitizer'
-```
-
This new sanitizer uses [Loofah](https://github.com/flavorjones/loofah) internally. Loofah in turn uses Nokogiri, which
wraps XML parsers written in both C and Java, so sanitization should be faster
no matter which Ruby version you run.
@@ -136,6 +200,12 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more
The documentation for `PermitScrubber` and `TargetScrubber` explains how you
can gain complete control over when and how elements should be stripped.
+If your application needs to old behaviour include `rails-deprecated_sanitizer` in your Gemfile:
+
+```ruby
+gem 'rails-deprecated_sanitizer'
+```
+
### Rails DOM Testing
TODO: Mention https://github.com/rails/rails/commit/4e97d7585a2f4788b9eed98c6cdaf4bb6f2cf5ce
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 7d6521b2a8..1e1afef26d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,26 @@
+* Remove --skip-action-view option from Rails::Generators::AppBase
+
+ Fixes #17023.
+
+ *Dan Olson*
+
+* Specify dummy app's db migrate path in plugin's test_helper.rb.
+
+ Fixes #16877.
+
+ *Yukio Mizuta*
+
+* Inject `Rack::Lock` if `config.eager_load` is false.
+
+ Fixes #15089.
+
+ *Xavier Noria*
+
+* Change the path of dummy app location in plugin's test_helper.rb for cases
+ you specify dummy_path option.
+
+ *Yukio Mizuta*
+
* Fix a bug in the `gem` method for Rails templates when non-String options
are used.
@@ -56,7 +79,7 @@
namespace: my_app_development
# config/production.rb
- MyApp::Application.configure do
+ Rails.application.configure do
config.middleware.use ExceptionNotifier, config_for(:exception_notification)
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 6a4660bad0..bc966e87c6 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -217,7 +217,7 @@ module Rails
# namespace: my_app_development
#
# # config/production.rb
- # MyApp::Application.configure do
+ # Rails.application.configure do
# config.middleware.use ExceptionNotifier, config_for(:exception_notification)
# end
def config_for(name)
@@ -414,8 +414,14 @@ module Rails
end
end
+ # Return an array of railties respecting the order they're loaded
+ # and the order specified by the +railties_order+ config.
+ #
+ # While when running initializers we need engines in reverse
+ # order here when copying migrations from railties we need then in the same
+ # order as given by +railties_order+
def migration_railties # :nodoc:
- (ordered_railties & railties_without_main_app).reverse
+ ordered_railties.flatten - [self]
end
protected
@@ -448,11 +454,6 @@ module Rails
super
end
- def railties_without_main_app # :nodoc:
- @railties_without_main_app ||= Rails::Railtie.subclasses.map(&:instance) +
- Rails::Engine.subclasses.map(&:instance)
- end
-
# Returns the ordered railties for this application considering railties_order.
def ordered_railties #:nodoc:
@ordered_railties ||= begin
@@ -472,13 +473,13 @@ module Rails
index = order.index(:all)
order[index] = all
- order.reverse.flatten
+ order
end
end
def railties_initializers(current) #:nodoc:
initializers = []
- ordered_railties.each do |r|
+ ordered_railties.reverse.flatten.each do |r|
if r == self
initializers += current
else
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index a26d41c0cf..0f4d932749 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -47,7 +47,8 @@ INFO
logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))
logger.level = ActiveSupport::Logger::WARN
logger.warn(
- "Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " +
+ "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " +
+ "(ie, make it writable for user and group: chmod 0664 #{path}). " +
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
)
logger
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 2c16b83f8a..786dcee007 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -93,9 +93,10 @@ module Rails
# Loads and returns the entire raw configuration of database from
# values stored in `config/database.yml`.
def database_configuration
- yaml = Pathname.new(paths["config/database"].existent.first || "")
+ path = paths["config/database"].existent.first
+ yaml = Pathname.new(path) if path
- config = if yaml.exist?
+ config = if yaml && yaml.exist?
require "yaml"
require "erb"
YAML.load(ERB.new(yaml.read).result) || {}
@@ -104,7 +105,7 @@ module Rails
# by Active Record.
{}
else
- raise "Could not load database configuration. No such file - #{yaml}"
+ raise "Could not load database configuration. No such file - #{paths["config/database"].instance_variable_get(:@paths)}"
end
config
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index a00afe008c..d1789192ef 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -66,7 +66,11 @@ module Rails
end
def allow_concurrency?
- config.allow_concurrency.nil? ? config.cache_classes : config.allow_concurrency
+ if config.allow_concurrency.nil?
+ config.cache_classes && config.eager_load
+ else
+ config.allow_concurrency
+ end
end
def load_rack_cache
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 8cc8eb1103..5276eb33c9 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -4,12 +4,16 @@ module Rails
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/
RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/
+ EMPTY_STRING = ''.freeze
+ SLASH = '/'.freeze
+ DOT_SLASH = './'.freeze
def initialize
super
- add_filter { |line| line.sub("#{Rails.root}/", '') }
- add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, '') }
- add_filter { |line| line.sub('./', '/') } # for tests
+ @root = "#{Rails.root}/".freeze
+ add_filter { |line| line.sub(@root, EMPTY_STRING) }
+ add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) }
+ add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
add_gem_filters
add_silencer { |line| line !~ APP_DIRS_PATTERN }
@@ -21,7 +25,8 @@ module Rails
return if gems_paths.empty?
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
- add_filter { |line| line.sub(gems_regexp, '\2 (\3) \4') }
+ gems_result = '\2 (\3) \4'.freeze
+ add_filter { |line| line.sub(gems_regexp, gems_result) }
end
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index c479e92ae0..e39f0920af 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -24,7 +24,7 @@ module Rails
opts.on("-p", "--port=port", Integer,
"Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
opts.on("-b", "--binding=IP", String,
- "Binds Rails to the specified IP.", "Default: 0.0.0.0") { |v| options[:Host] = v }
+ "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v }
opts.on("-c", "--config=file", String,
"Uses a custom rackup configuration.") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true }
@@ -126,10 +126,6 @@ module Rails
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Run `rails server -h` for more startup options"
- if options[:Host].to_s.match(/0\.0\.0\.0/)
- puts "=> Notice: server is listening on all interfaces (#{options[:Host]}). Consider using 127.0.0.1 (--binding option)"
- end
-
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 672b78c599..4411ec33ef 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 841d39dad6..0d95bb48e0 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -44,9 +44,6 @@ module Rails
class_option :skip_gems, type: :array, default: [],
desc: 'Skip the provided gems files'
- class_option :skip_action_view, type: :boolean, aliases: '-V', default: false,
- desc: 'Skip Action View files'
-
class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false,
desc: 'Skip Sprockets files'
@@ -167,7 +164,7 @@ module Rails
end
def include_all_railties?
- !options[:skip_active_record] && !options[:skip_action_view] && !options[:skip_test_unit] && !options[:skip_sprockets]
+ !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets]
end
def comment_if(value)
@@ -281,8 +278,14 @@ module Rails
[]
else
gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
- gems << GemfileEntry.github("#{options[:javascript]}-rails", "rails/#{options[:javascript]}-rails",
- "Use #{options[:javascript]} as the JavaScript library")
+
+ if options[:javascript] == 'jquery'
+ gems << GemfileEntry.version('jquery-rails', '~> 4.0.0.beta2',
+ 'Use jQuery as the JavaScript library')
+ else
+ gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ "Use #{options[:javascript]} as the JavaScript library")
+ end
gems << GemfileEntry.version("turbolinks", nil,
"Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks")
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 7b1a6aa854..2567469c95 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -15,9 +15,6 @@ source 'https://rubygems.org'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
-# Use Rails Html Sanitizer for HTML sanitization
-gem 'rails-html-sanitizer', '~> 1.0'
-
# Use Unicorn as the app server
# gem 'unicorn'
@@ -34,7 +31,7 @@ group :development, :test do
<%- end -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
- gem 'web-console', github: 'rails/web-console', branch: 'master'
+ gem 'web-console', '~> 2.0.0.beta4'
<%- if spring_install? %>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 80393a16ba..111b680e4b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -9,7 +9,7 @@ require "active_job/railtie"
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
-<%= comment_if :skip_action_view %>require "action_view/railtie"
+require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
@@ -33,7 +33,7 @@ module <%= app_const_base %>
# config.i18n.default_locale = :de
<%- unless options.skip_active_record? -%>
- # For not swallow errors in after_commit/after_rollback callbacks.
+ # Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
<%- end -%>
end
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 2cd8040f35..92ff0de030 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,8 @@ Rails.application.configure do
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
- # For large-scale production use, consider using a caching reverse proxy like NGINX, varnish or squid.
+ # For large-scale production use, consider using a caching reverse proxy like
+ # NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or NGINX will already do this).
@@ -38,7 +39,7 @@ Rails.application.configure do
<%- end -%>
# Specifies the header that your server uses for sending files.
- # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
@@ -57,7 +58,7 @@ Rails.application.configure do
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
- # config.action_controller.asset_host = "http://assets.example.com"
+ # config.action_controller.asset_host = 'http://assets.example.com'
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
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 053f5b66d7..32756eb88b 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
@@ -31,6 +31,9 @@ Rails.application.configure do
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ # Randomize the order test cases are executed
+ config.active_support.test_order = :random
+
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
index 5508829f6b..b2aa82344a 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -7,7 +7,7 @@ require 'rails/all'
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
-<%= comment_if :skip_action_view %>require "action_view/railtie"
+require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index 1e26a313cd..28cdfecf81 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -1,7 +1,10 @@
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
-require File.expand_path("../dummy/config/environment.rb", __FILE__)
+require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
+<% unless options[:skip_active_record] -%>
+ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
+<% end -%>
require "rails/test_help"
Rails.backtrace_cleaner.remove_silencers!
@@ -10,6 +13,6 @@ Rails.backtrace_cleaner.remove_silencers!
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
# Load fixtures from the engine
-if ActiveSupport::TestCase.method_defined?(:fixture_path=)
+if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
end
diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb
index 05d9fde280..466b74bd12 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -6,7 +6,7 @@ module TestUnit # :nodoc:
check_class_collision suffix: 'JobTest'
def create_test_file
- template 'unit_test.rb.erb', File.join('test/job', class_path, "#{file_name}_test.rb")
+ template 'unit_test.rb.erb', File.join('test/jobs', class_path, "#{file_name}_test.rb")
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
index feb250e89a..f5351d0ec6 100644
--- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
+++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
@@ -2,7 +2,6 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= class_name %>JobTest < ActiveJob::TestCase
-
# test "the truth" do
# assert true
# end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 9502876ebb..357aebf584 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -22,17 +22,6 @@ module Rails
rescue Exception
end
- def frameworks
- %w( active_record action_pack action_view action_mailer active_support active_model )
- end
-
- def framework_version(framework)
- if Object.const_defined?(framework.classify)
- require "#{framework}/version"
- framework.classify.constantize.version.to_s
- end
- end
-
def to_s
column_width = properties.names.map {|name| name.length}.max
info = properties.map do |name, value|
@@ -61,6 +50,11 @@ module Rails
end
end
+ # The Rails version.
+ property 'Rails version' do
+ Rails.version.to_s
+ end
+
# The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)".
property 'Ruby version' do
"#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
@@ -75,23 +69,10 @@ module Rails
::Rack.release
end
- # The Rails version.
- property 'Rails version' do
- Rails.version.to_s
- end
-
property 'JavaScript Runtime' do
ExecJS.runtime.name
end
- # Versions of each Rails framework (Active Record, Action Pack,
- # Action Mailer, and Active Support).
- frameworks.each do |framework|
- property "#{framework.titlecase} version" do
- framework_version(framework)
- end
- end
-
property 'Middleware' do
Rails.configuration.middleware.map(&:inspect)
end
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index 977feb922b..1dc1d70f8d 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -2,6 +2,10 @@
<html><head>
<meta name="viewport" content="width=device-width" />
<style type="text/css">
+ body {
+ margin: 0;
+ }
+
header {
width: 100%;
padding: 10px 0 0 0;
@@ -95,4 +99,4 @@
<iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index eb620caa00..89792066d5 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -19,13 +19,13 @@
}
a {color: #03c}
+
a:hover {
background-color: #03c;
color: white;
text-decoration: none;
}
-
#page {
background-color: #f0f0f0;
width: 750px;
@@ -57,21 +57,21 @@
padding-right: 30px;
}
-
#header {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAABACAYAAABY1SR7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAGZhJREFUeNqsWwmUXGWV/t5Sr9aurl6qO0l3Z9/DEoJh18gZQGAUxPHIyQHH7eioZ8bjnAFHZ0RndNxxRBhGcUbxoKIHBkTEcUYREIHIGpKQjUDS6U660/tSVV3Lq/fefPf/Xy2dBFGYx3npqvde/e/e/97v3u/e/8e4Lt2L8DCCAFcGwF8ZBjYbgM1rAZoO+WLwZhDMu9y4+YcOozbAqzwXNA3GdzX/5hV+KnKO2+GXFj/AvzmW8e72iG202CYiphbY403f9/k3QHZtJ9oWtyCQe7wGX79TKVb7rP9pXJPDVxf0Rz+oyxm4HNWrahFNixdk3EAJbERMWOm4ulctVODNVeEVK0DeRVDgb1wfJgcqUo6duaKnFOH7bm6JmH+5LOEgZprwRIHAV3JYfLjKM55Noz3bBqdcgt0Wg52Kq/cHHkXns0qIukKBlltk9rU2QaiouiefPQ+RdBuseAJeqYTK1CTH8mE4NsyIpRWu8nssCs+xULWpjGVwTvieKl/sV6mIXzOib/OftzuG8d6l8SiVMODyRb46oazg8YPP2Wnvy9ISNqplzsxYAW6hjGhHEmYiBoPC+hRMfFMrESgrBC5n0KS+lq1nPahZh2OXymg9bSNWX/u3FKyKI//7Exx96B4Y8RiCEseq8t0VznyxjMDidFIJ8QSf3hJEOFbZEAHVhIkFTX54fxtnIW5pJUQIeZ8ooZShkInuDOLpFIX1ldtCBix7KI/k4E7OwbTjcNIdiCQzsONp2LEk7GgUnZsuQN9lW2En45xlukrUghWzeZq8FsXsi8+gND6MSCqD9k3nwulIUShKZxt0LYPWortRSY0NXreC8J6pZNDChEDh53PT1NIPLaEnLbQKNTETEaR7sycA0jD1INXZAnzObjTbiWh7Vr1A3Knn4nciu+lCvstUig09cp96cVCtcELoFpEIFUjjyIM/osWIg+IMXS3DcfNwZ3NQHmmKU9OqroX2jWdgatduuPkpmA4ViZrK9RqKABEBtg9tDeW+oUIyTIYuFaX7eCG4aqbU+hhKocD3UBoZISBLiC9cpAQKyq5SQo6OjVswtec5VHLTiHUuIN4WonXlqUj2riS0DIUXwZlERFHSK+SQGzqI3MHdmNm7CzMvvowF527B8qvejZ3/+iXk9vVTao5tiTKN0OUHISZEGS/8W6UbRdoTSHe3E1f+CRaR3xhBLVJSIQ7qleZQGBigZYoYdR+ElUjBaW3H6JMPIrV0Hdo2bEayZ7my0KsdLctPBS64EuWZMYw/9wTGnvod0mtzWH71Vuz66o10bVpK8FIx6orUMejpCKYBTvfM9HXBJtA8z3/1BKDivaksVJmaYsgsYPDnd6LzzAuw8I1XUIGleC1HtDWLnguv5BiX4+jDD2D4sQeV1bQvNXBi6vAb1MGtrEEHjRPgqfZ0qMRJElYYSudfq12nmzAvtJ2yib69iRadRGnySD0Uv5bDtCPou/gqnPY3N6DnLRczgtHxCf4aVnUeUdgw6i6FqM1w292Ujo/TJdB5wHcJ2iDCaBTRmVfw4rkw4yksuvQyJJf0YvrgNiayvBLESS9AYuFqJLLLCPb4SQWulosojhxmeCeoDeaQSoVuy8lPtSKxYKnC2Bmf+DwtvBgv3/qfTI6uEtGuJV7PCBTIq5zNtt5uxBgyvap30pf55TISfX1Y/PatGPrVvcgPvEyAJ1GenaPZLSy//G2IL+qki43CNCMwk620iovy9FGUJgYwm8gwpK/guRJOS5dyD688h+n9z2L28F4Ujx2ia04jEl8Ad3oGVTePaGcnQ3sKLb1rkD3nIqx594dRIh733n6PmmrrvGj671sjVlxczRWAkxZ0r+rTrhfMJ0uEM8xKUYXONR+5nr57BdpP24TCsX6M/f5F5AYLWPauK9F11htUwjOIL8GNZH1qpKwiyVGELk0OoDj2EtziFOaODSN3aC/v24xmZzAU51TgcJKd/DktHo9jyRXvg0Or7PvejTj22KPKiyafew6zg8MYypVLNsLkJ2bxaZXM4i5EmCBPsEaoWJUUpfeSK7DgvEtQmh4ihTDQdf5FOHDHr7HqPVeh99KL4OVzpE50N18CtqnCdBCY6rsEcTsqIGUGD6rY9e3bMPzIHmTWLsbqa7ai84wL6YrTqEyOqEmwonEExSoO//R7dLcJWiWCueF+7P7mjZAUY8YdJZqySMo24j5zQSybQdeyhdrX5imho4NhEEnkRbkDQyjSRVJLeziCgef/6avIrFuOtR95P2lJNSSshg4l6rdm+Ht9inWsqIOX7voN+u/eRoEM5PvHMbbjGcwcfg7jO3YxbCcRiaaYQOXnpEaFGeahGQaMCidJRidt8RghS6Q344XQIowmFq2QXdLNdwsx8zUFqCOQNIECVqdp8pESB53Fvhdux9T2FxBb1AWX4XbjDX/HFzjEmgedB4XYKT5D4T0VTLRCtIiTwOBvfovpvS8T+Bm4MyW6jw13tIIDt/9G/TTWk8HKvzgbmd4+YldYQIdixgHJYkC82Ul6UDnQSbEGdsFGZlEWyUyLyiEyYwajRVAoAXNlEjR+pjUCUmiDQcKOORwwgpFfP4cg5mPzTZ9FoqePdGVWuZRPYQNPcgrd0/dCpqpdy3DIsQ4fxtiTu7Hxkx8iRXkcB+94iM86/K0Jx4opi5aOzGJs14toWeLAdYXWxFQCtJlkA+LUq+bI7QR3mj3YoqVNgGcXd5NWUOiZAk9GH86S4jK25jWBLVREl1uK5Voywz6WXf1WLHjTm0lPigSyxoUpnEqU8c26Wyk/Y24RMjhw/yMoj+cQbWvH0isuwuijL6BwaJwcyq7XUTaBP7N3HOU3ke7HSONJb8RTBGoGKZPFyTE8saTZyCPtrC2coxOoTuY5+x4UTzHNsNjR6d6Qa8JJ5BIV8ksVtKzpwcr3v5dyOrzHKMWXizsZAnK6k1ImPDmAqjOmdr9AwXcodzr4kwfQfuY6VKbzyhpGU96S75WxIqb2DaPnvNWKklQD4WSuzB+sVILjOYjm/VARSWKTBQQzlZCFmErYeubzVJJR14SlQtVQMjO0xrXvoulXkq3OKnxAXqSsoSmNUbOM/BV35RjDDz9JrBXpnnEM3vsYjj38LLyZihI8QNAgQhITOCmTO46i+6w1MPm86RVIiC09/RJUGcECCe2UU0G6QIyUjEC5hGaCNd4RqHKU6VuDylQlI2N8hfXDWibEdyhCKXREuZUVUX8lyhh2+Jl5Q/6akSgT4izGn3wBFu+JwYOKj8qwtsbJaYmJuYEZ5AYmFOWXPCN1jTodzeuqM0WtSI1rzXrV0LSNKRFuZLYQ2EYVPjEQVuQUMsCya65GvL1HWUwJS+FNUcBsUiZUQv7aLGlndr+I8ug4XUMVAJw4U7FmI8SFETTmUaGK2gas1SeeP8znoizIEso9DaUIy2FWkNU5V0VYs/azWXKncuCHqgQq1CHiY831H8TGr34erRvXKdD6LD3b+HnRn12qGgdqlmxHZe2aRcy6NbQScl8y8dSOfWQE1yK9YYmqXYww3xhNObemUI2IWraF2d1HMTeeh83MbkUiylKiiMdy2wjzXBjxWYdRiSkhfDVVKGSstxM9l16JxZe/E2+848c49bPXK9D2vPUyEsBOVZMINmpCW6HgEOuIQjXF6FYuAV2aHsWyrVfj9C9er5SR5Kms0PTf8QoZtIo7WSJW+mmRJLGSpDK2ipzV2bK6X6fxtWOCicYVqyhGXkXn+WeTcfape5ZDsPGM91C5iy8LI0s445bd9FkrAFHICt1N8DE+gdyeQczs34+uzeei68LNLGfdea50st6VbiyYmHq+nxTFRSSRVsD3ii7xyeQbdt/M5h/MERMT4i6GjlAWeUxh6HCN8+LIz+5H5zlUbtHSOnVp4MCa51JaIQ16i0kwP9CP0uExPP+JL2DggfuYN8jTJClYxnH4aNimdpp0r7nDkyx9h5gE0+RqSVTyZXXTsMz5FaJyMJrrGLNopyWUIImj//1LjPzuUZLCC5gzVqMwPIglV7/rxCaihFaCPCDOxDUl1EoylFP4mUlFCgPDStLKWB47PnUjrSSsNqrJsa/zR02ZwGjYRoVkEZh0ZHzbfmTPXE85SWrnKip6GeFE2I1iKVBCzNK9pmiVhS1x+Axx7myRJesvgHvvR3rNKmQ3n/OKPVGND1MVXTqHiFK6qVFiwlXgTVDhkq+ChhnyJCW9GeaoIGQOdV0M9YhYZWbvUXrIJJ+rKL6lJ9CYj5Fai0iKqyPkx0HcUsJYrBbtREIJ2H72GxTI/2CL1zAbLkZ8WIxYgUvsKebq6Zl3rEZvymx6echo1N+au9XcS3oHsxWMPrGTFH+CLhsmbhMNRWrNB4SZVSwyJ5WDFRb3DAAmaXf2rPP+6BpbkmStkBLAWwkHmdNWKfYqFaZRp2GGdo+mhpv6bBkNhepRzERpdASeW1aKSZ5RidpoUsRAvQ+NJCnJHHl+bcZ80vjkij661vo/rWMQSitWskgnNv7LP+MNN38NadYuCPtYCItIFTjMRgfeqClkhkFZ+FXCQmpFuyKXii7xNI93LT9szdrUMsNZnJkuwZX6zlKdaqRXrESiq/e19kBC3NisLt+Gc/7jW0gtZ51Bl1MCmUaoM//aRv0aapnF0l362KIUnI6EyuhCUOuWrIVfAZcRAj5NJWJ0C5epP19y1awJLWhdt/a1t3KcGF8Yxb5bbsLItoeYmxZRkRWq46IrR9StX/tcw4oKsYH+nlrZpmbcZQ7R1tDPBvMbdIwofLpVKIfcJy5nCa5WRhnDFkVOx+s5kr29GPzpfUxsuxg0zlQUxSZudG/CqNOSIJxYCclGCA7fDRDpiCK6gIVfidVmWXrHRh0fmBd+eSYIIEcWdRhdJJsWp+aQT1vI9nYjnl3wuhSJLuhAJJ1WQWDisadUELCi0bD1WlscMpq6lrV1Ft0riC9tVcFD8odfDVS9bod5pNGgC3+XFnxsXA2rsw25/gHMTcwiRxdbvLgPsY7s61IktWSZinw6l8SbupNGvUlphB1yZY3aIhfZtRmz4XS3oMoA5JP6BywdvBIr24ytMdzsWjHaMcnI0nXRG5FkdCrnS6gy6QzccxeMZDsJW+r1KbJ4pbKAVy6huXoyauVUaAUjRK5WjN9cH05PCiZl84VfsXaSVTKf191C6F61qCXjtjAORtvTSPb0sgYoEi/UmEmnMj6JkpXA6z2cTAbxxV26GdEEZB12DVVV63BrIuwYaWpCGZyuJBWSFSxPLTB5PH1+rhDDKlQbuvajNUzE+UVyRTTdQt+zWIrGWIJOozo8hjmashq8PkXsZAoty1Yqi/gVnq6ru+p1pUKFTM3dENJzu421TiqKKq3hhUp45apSyM1VGMH0xOi+liz0yOxUyijs2w2DlRjI+8tHB3XUIP+fGBxA9+LFr1kRgwV769p1fPkEQ+9KRq+dKE9MsGKc1BmxltEC7W6CEdW0aUtocIvw0tcSt5JGu3R4OA+zIxW1uKoUOUZzFxmxRp/ai+iz+xi9CK5EVJGdqBNBlG4xdvBlRq9eTQteawhm0MgPLsSGj92gVqjKk8ew/TOfxPjjz8BKxhvLFGHjWUBuJh0Cu6pqD7WCTGz4BDqKpE30rIlj05rw6sKFxuCXPP9O8MEjxQqOTuQwNjJLa1mItaRRGB3GLHnO6znaNmxC/nA/cocPKNoS61iEZVdfEy5LBHVKUieCLY5eeKIiXp6RapJuNVJFMCamYGnOUFyslBo0Xronai0dIfXmnZIqtKhgNIaj/F3ULSLx4j60dnXXy8s/OZe0dyGW7cLOL34arevXI9rayWgYhZPtoJtNqsTbyPKUgwzamyCw867MtG5NBUF9bSBXLCkeKOzDroUutaZODax52yUk5sfgsyrL897+PXtQHTmK7vWnomPpCkSTf3pI7j7/Qmz/5HWY3r5LNziYeC3WPlYsovOJJ7VKVbuPENcgXEyvuV3IbKXpPlcqqh0acqGe2S1oq1jzqmZ+b0mGDJNaM2bnjrHuPnYUifZOtDMKda9ah1RnZ30F99WO9jM2MzouZw0vLdJIuCsiUInOz0vbiVNa9DSBtITyWo3VAV/XG/KmPEuBKrmard7rNxKiyCoN7EBnpXlLCiYTmfibuEHSSSkLV4uzGNr5NEYP7EZb31J0rd6AzMIevtf+g4oIg+7e8iYM3H03J5muw9n3ZquqfwU3aGDdMBqdztr+lXBbhyg+R2xYTb5jN7YG6SKnyh870r8Ki6Py0CiO3fcTNWaCBU3E8FVDr7ZPRjbcDLHO30N/TmazdLk+JFMxVoZh6errUrcmnDQp5o4MocrI4o3N6dmXhp1hoHkOFV2R5CXtVwm3Qc0aBip8Z6lY0HtRpJ8GYz5pVFgxgkaHiaCuDE1gfOAhFdNbJIKxplCKNJqqyoqi0CT9tp9/IyyPE2SryYyDKD9LVKxKUqXbuFOM+yVDN/Rq+0ia1mLmtYNqK8rhTiSpLLNbLkDLuZvQ0X8QBoG+//5fIMjP4AQ/kJkuM+vW+sS1wkgiVSTi0Fq2XqoLFfFYMMkyHSFL2mOpHQmy+aU4xXHoLk6rrIkYiE1JNpZOJjO1ivduOLSkZeuk6/YBwR54jaVv6chXpmZQmJnEssveQjwVcPCXv1IWt4//sUVB7K4WpGTREqhvJCrO5MhtGLMTKWU5pUSpDKs1glhbB4W3VCSpTM6gOl2GQzxJt+RQUMFcOoENrXG0FEhESSvMmIVIZ6uaHL9QZn6Y067VNJueV4bdmYDdktJ7pAJNKKfG+pG/cz8GH/gfGLIARF4o9fs8RWSrUmZxN7Z+9za0sooTPiRuI22bsUMHsevWW1B+iFnYdOgqFWTPPxWnXPdxtK5eV8fB9IH92Pn1m1hz7MQh00Xm/C34+K23MiOXsPvLX8bgbXej5bz1OPs7tzIhduHgnXfghX/8OplEsr6U4ZtV9G69HMvf8wEkKUfgaUeWbs4zX/8Sxm/+AbzxCRVF1VpFM9hrvS2ZmZbuRUh2LpxPw7t60EWK8vgHPoCZ5w4i1pvBps99Bu2nbJ73XLyzB4kvLcAPt27F2LFR9MTjSKbb1L1h4mIq4iNL14u2ZRFJysazZCNHqA0DZXRcuBGnf+bz6v4JLDqVgk3r247DnMdJDkOzffJtDfoY2b0dg08/gbZlq7BiyyWk+MuQ2bAGU9v2snTtQnxBj3pu9OnfYXr/Hiq1EZ0bz0ZsUS+sFUvgDB+DFfh1v3X9Kg4xknfLRNZ21h2/RYTX29avU0pUSwUcuP07KLw0oBZrA5bGozt2MlA5updgzGuJnYyp6rt7778HP37fX+OJW77ZaKzKoo5eOdfRhMehO3+EbXzu8H/dXW/SOTwj0gZqeoVck+h3xES9LDjpVp3QXeRdqSVLkDllrepy5oeHMPH0brq2qdteRmNJwj7pYKFVlr75YrztKw6ya9aFTzF8Tk+pBZrmXRGRdCsSLMiQbKlfE7PLrjarCcSSA0QZvQQevGKncnrfXpVwZTde3+XvqN9b8d4PYfuNn8O+b9zO56K6oGpOiMYreNfSc7eoE+FO00P334XJx3fQzM685zd8/Hqs/uCHGGEy9QEslaT0Cm9t7rVyYqnGWogEGIl+nqUTmyxwTj62HTs/91ks3XqN2u8VBLKZoVt14pe/42oc/O6dzB2+qnEMNGHEPHHbSfiSqloakGP7D7+Dpz79BfT6cRXu5rHatk51Nh9aEaOJu2mOZIf36uDu6EDi3PVoIQGV5efiwSG1Rjny8COY3P4sI1WM2HKx4bpPYdEFlzA9RMOlhCAsLJssYqGxRbcZI8//9MfIrliDvjPOwqqL/xwD996P6rY9zGHWPNMNPf8UJl9+Cdm169G9YWOdapjB8auShsJMc85YdekVWL7lQgroKHd68qMfRcAEu+lrX1GdSdmBKjQn0aOrU9lso5bK53uSLiyscNu10tAy66FganAQD9zwD6jM5ZBe2IeLbvoGWs5YofZQyfKXxbpejl133omfXfth7P/Fz8NRLbXgb0nGNe26GhGST5MzFmEYll2oCl+sd2IZCcWtTKxd6rokwdYVpyK9fB1z1KnIrD0NDt1WiNGB738X3kxJVapiWVmR5pCurc2iSaIkmNJ0Hr+9+WYkMu0YfHI7Dv9+J+766Eew8vSNiFP4WGsGBanhh6bw1K3fRjSdwfSel5FikTT67At4+t9vgVssojA0Rp6VwOyhfjx9262qABrfw1KaJW15YprXvsVcEG1sT5eCji40fXSURVyAvTd9TSmv6nTVifQx/uwzmHiU7kb3Clu+GC27MsY247p07+SihN0m/Kgc6EXRIjmMgDvCF9mcsXJxDgniZSnN3xFLIcc6Yormd1mhCX2QpWc7SteolNUpNUQkIUvJpDkUrsrfqy1L8ZjaFSTrJKLsCbvz6BqxaBwdBReWbJmF3kTa2NYRVYFGHEYKqqFKFXtzMg6uUhaJyzZyQ/d/FdUm8LwmAuYwO/vhQBU+m+ddmy+NpBKNWpIzF7EdRSxrOygMMl6LruUw2tQXOTy1akNFk/XtU/V70H3g6YyNNk5GtOIp/DYvlKp9LoJLWuIl2fADfJ/X71PQ8Jo2Vzbv620OAFI9jtIqCQ7tnfC/JxhNT4dShds4UKvB66s1ftPnRqOh/l13hDDqWGhxqUgTsIV1Fzg5Y7TEpKsK+B/w+sdqUWuqv1CxUN8K/MqHLMnhj/g/J/4/juDky9VSg0kh/zQj322897Pao/8nwAC+AZicLeuzngAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
background-position: top left;
height: 64px;
}
+
#header h1, #header h2 {margin: 0}
+
#header h2 {
color: #888;
font-weight: normal;
font-size: 16px;
}
-
#about h3 {
margin: 0;
margin-bottom: 10px;
@@ -84,18 +84,26 @@
margin-left: -55px;
margin-right: -10px;
}
+
#about-content table {
margin-top: 10px;
margin-bottom: 10px;
font-size: 11px;
border-collapse: collapse;
}
+
#about-content td {
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;
}
- #about-content td.name {color: #555}
+
+ #about-content td.name {
+ font-weight: bold;
+ vertical-align: top;
+ color: #555;
+ }
+
#about-content td.value {color: #000}
#about-content ul {
@@ -107,21 +115,23 @@
background-color: #fcc;
border: 1px solid #f00;
}
+
#about-content.failure p {
margin: 0;
padding: 10px;
}
-
#getting-started {
border-top: 1px solid #ccc;
margin-top: 25px;
padding-top: 15px;
}
+
#getting-started h1 {
margin: 0;
font-size: 20px;
}
+
#getting-started h2 {
margin: 0;
font-size: 14px;
@@ -129,40 +139,46 @@
color: #333;
margin-bottom: 25px;
}
+
#getting-started ol {
margin-left: 0;
padding-left: 0;
}
+
#getting-started li {
font-size: 18px;
color: #888;
margin-bottom: 25px;
}
+
#getting-started li h2 {
margin: 0;
font-weight: normal;
font-size: 18px;
color: #333;
}
+
#getting-started li p {
color: #555;
font-size: 13px;
}
-
#sidebar ul {
margin-left: 0;
padding-left: 0;
}
+
#sidebar ul h3 {
margin-top: 25px;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
+
#sidebar li {
list-style-type: none;
}
+
#sidebar ul.links li {
margin-bottom: 5px;
}
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 285e2ce846..957deb8a60 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -3,7 +3,7 @@ require 'rails/test_unit/sub_test_task'
task default: :test
-desc 'Runs test:units, test:functionals, test:generators, test:integration together'
+desc 'Runs test:units, test:functionals, test:generators, test:integration, test:jobs together'
task :test do
Rails::TestTask.test_creator(Rake.application.top_level_tasks).invoke_rake_task
end
@@ -13,7 +13,7 @@ namespace :test do
# Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
end
- task :run => ['test:units', 'test:functionals', 'test:generators', 'test:integration']
+ task :run => ['test:units', 'test:functionals', 'test:generators', 'test:integration', 'test:jobs']
# Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
desc "Run tests quickly by merging all types and not resetting db"
@@ -28,7 +28,7 @@ namespace :test do
Rails::TestTask.new(single: "test:prepare")
- ["models", "helpers", "controllers", "mailers", "integration"].each do |name|
+ ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
Rails::TestTask.new(name => "test:prepare") do |t|
t.pattern = "test/#{name}/**/*_test.rb"
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index d8800eaa0f..0749615d03 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -30,26 +30,24 @@ end
class ActiveSupport::TestCase
# FIXME: we have tests that depend on run order, we should fix that and
# remove this method call.
- self.my_tests_are_order_dependent!
+ self.test_order = :sorted
private
- unless defined?(:capture)
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index db2106aed3..0eddf644d9 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -182,7 +182,7 @@ module ApplicationTests
test "application is always added to eager_load namespaces" do
require "#{app_path}/config/application"
- assert Rails.application, Rails.application.config.eager_load_namespaces
+ assert_includes Rails.application.config.eager_load_namespaces, AppTemplate::Application
end
test "the application can be eager loaded even when there are no frameworks" do
@@ -980,6 +980,15 @@ module ApplicationTests
assert_kind_of Hash, Rails.application.config.database_configuration
end
+ test 'raises with proper error message if no database configuration found' do
+ FileUtils.rm("#{app_path}/config/database.yml")
+ require "#{app_path}/config/environment"
+ err = assert_raises RuntimeError do
+ Rails.application.config.database_configuration
+ end
+ assert_match 'config/database', err.message
+ end
+
test 'config.action_mailer.show_previews defaults to true in development' do
Rails.env = "development"
require "#{app_path}/config/environment"
diff --git a/railties/test/application/default_stack_test.rb b/railties/test/application/default_stack_test.rb
deleted file mode 100644
index 4778cdd74c..0000000000
--- a/railties/test/application/default_stack_test.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-require 'isolation/abstract_unit'
-require 'rack/test'
-require 'active_support/json'
-
-module ApplicationTests
- class DefaultStackTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
- include Rack::Test::Methods
-
- def setup
- build_app(initializers: true)
- boot_rails
- end
-
- def teardown
- teardown_app
- end
-
- test "the sanitizer helper" do
- controller :foo, <<-RUBY
- class FooController < ApplicationController
- def index
- render text: self.class.helpers.class.sanitizer_vendor
- end
- end
- RUBY
-
- app_file 'config/routes.rb', <<-RUBY
- Rails.application.routes.draw do
- get ':controller(/:action)'
- end
- RUBY
-
- require "#{app_path}/config/environment"
-
- get "/foo"
- assert_equal 'Rails::Html::Sanitizer', last_response.body.strip
- end
- end
-end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index a905598d80..caef39d16f 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -94,13 +94,20 @@ module ApplicationTests
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
end
- test "removes lock if cache classes is set" do
+ test "includes lock if cache_classes is set but eager_load is not" do
add_to_config "config.cache_classes = true"
boot!
+ assert middleware.include?("Rack::Lock")
+ end
+
+ test "does not include lock if cache_classes is set and so is eager_load" do
+ add_to_config "config.cache_classes = true"
+ add_to_config "config.eager_load = true"
+ boot!
assert !middleware.include?("Rack::Lock")
end
- test "removes lock if allow concurrency is set" do
+ test "does not include lock if allow_concurrency is set" do
add_to_config "config.allow_concurrency = true"
boot!
assert !middleware.include?("Rack::Lock")
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index c727e0e46d..267469b6f5 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -176,8 +176,10 @@ module ApplicationTests
test 'db:setup loads schema and seeds database' do
begin
- @old_env = ENV["RAILS_ENV"]
+ @old_rails_env = ENV["RAILS_ENV"]
+ @old_rack_env = ENV["RACK_ENV"]
ENV.delete "RAILS_ENV"
+ ENV.delete "RACK_ENV"
app_file 'db/schema.rb', <<-RUBY
ActiveRecord::Schema.define(version: "1") do
@@ -196,7 +198,8 @@ module ApplicationTests
assert_equal "development.sqlite3", File.basename(database_path.strip)
end
ensure
- ENV["RAILS_ENV"] = @old_env
+ ENV["RAILS_ENV"] = @old_rails_env
+ ENV["RACK_ENV"] = @old_rack_env
end
end
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 118f22995e..032b11a95f 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -109,6 +109,17 @@ module ApplicationTests
end
end
+ def test_run_jobs
+ create_test_file :jobs, 'foo_job'
+ create_test_file :jobs, 'bar_job'
+ create_test_file :models, 'foo'
+ run_test_jobs_command.tap do |output|
+ assert_match "FooJobTest", output
+ assert_match "BarJobTest", output
+ assert_match "2 runs, 2 assertions, 0 failures", output
+ end
+ end
+
def test_run_functionals
create_test_file :mailers, 'foo_mailer'
create_test_file :controllers, 'bar_controller'
@@ -132,11 +143,11 @@ module ApplicationTests
end
def test_run_all_suites
- suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration]
+ suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs]
suites.each { |suite| create_test_file suite, "foo_#{suite}" }
run_test_command('') .tap do |output|
suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output }
- assert_match "7 runs, 7 assertions, 0 failures", output
+ assert_match "8 runs, 8 assertions, 0 failures", output
end
end
@@ -245,7 +256,7 @@ module ApplicationTests
def run_test_command(arguments = 'test/unit/test_test.rb')
run_task ['test', arguments]
end
- %w{ mailers models helpers units controllers functionals integration }.each do |type|
+ %w{ mailers models helpers units controllers functionals integration jobs }.each do |type|
define_method("run_test_#{type}_command") do
run_task ["test:#{type}"]
end
diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb
index 6f3e45f320..d5072614cf 100644
--- a/railties/test/configuration/middleware_stack_proxy_test.rb
+++ b/railties/test/configuration/middleware_stack_proxy_test.rb
@@ -1,3 +1,4 @@
+require 'active_support'
require 'active_support/testing/autorun'
require 'rails/configuration'
require 'active_support/test_case'
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index d687a8f506..b492258efb 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -268,11 +268,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generator_if_skip_action_view_is_given
- run_generator [destination_root, "--skip-action-view"]
- assert_file "config/application.rb", /#\s+require\s+["']action_view\/railtie["']/
- end
-
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 985644e8af..ed4e100a9b 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -54,7 +54,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "README.rdoc", /Bukkits/
assert_no_file "config/routes.rb"
- assert_file "test/test_helper.rb"
+ assert_file "test/test_helper.rb" do |content|
+ assert_match(/require.+test\/dummy\/config\/environment/, content)
+ assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content)
+ end
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
end
@@ -270,6 +273,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "spec/dummy"
assert_file "spec/dummy/config/application.rb"
assert_no_file "test/dummy"
+ assert_file "test/test_helper.rb" do |content|
+ assert_match(/require.+spec\/dummy\/config\/environment/, content)
+ assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/dummy\/db\/migrate/, content)
+ end
end
def test_creating_dummy_application_with_different_name
@@ -277,6 +284,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "spec/fake"
assert_file "spec/fake/config/application.rb"
assert_no_file "test/dummy"
+ assert_file "test/test_helper.rb" do |content|
+ assert_match(/require.+spec\/fake\/config\/environment/, content)
+ assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/fake\/db\/migrate/, content)
+ end
end
def test_creating_dummy_without_tests_but_with_dummy_path
@@ -284,6 +295,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "spec/dummy"
assert_file "spec/dummy/config/application.rb"
assert_no_file "test"
+ assert_no_file "test/test_helper.rb"
assert_file '.gitignore' do |contents|
assert_match(/spec\/dummy/, contents)
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index b38cc4277e..40469e31d7 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -9,6 +9,7 @@
require 'fileutils'
require 'bundler/setup' unless defined?(Bundler)
+require 'active_support'
require 'active_support/testing/autorun'
require 'active_support/test_case'
@@ -140,6 +141,7 @@ module TestHelpers
config.eager_load = false
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
+ config.active_support.test_order = :random
config.action_controller.allow_forgery_protection = false
RUBY
end
@@ -296,6 +298,8 @@ class ActiveSupport::TestCase
include TestHelpers::Rack
include TestHelpers::Generation
+ self.test_order = :sorted
+
private
def capture(stream)
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 4bec302ff8..92e4af25b5 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -38,21 +38,10 @@ class InfoTest < ActiveSupport::TestCase
end
def test_rails_version
- assert_property 'Rails version',
+ assert_property 'Rails version',
File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp
end
- def test_framework_version
- assert_property 'Active Support version', ActiveSupport.version.to_s
- end
-
- def test_frameworks_exist
- Rails::Info.frameworks.each do |framework|
- dir = File.dirname(__FILE__) + "/../../" + framework.delete('_')
- assert File.directory?(dir), "#{framework.classify} does not exist"
- end
- end
-
def test_html_includes_middleware
Rails::Info.module_eval do
property 'Middleware', ['Rack::Lock', 'Rack::Static']
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index ec64ce5941..1976466229 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -144,6 +144,42 @@ module RailtiesTest
end
end
+ test "dont reverse default railties order" do
+ @api = engine "api" do |plugin|
+ plugin.write "lib/api.rb", <<-RUBY
+ module Api
+ class Engine < ::Rails::Engine; end
+ end
+ RUBY
+ end
+
+ # added last but here is loaded before api engine
+ @core = engine "core" do |plugin|
+ plugin.write "lib/core.rb", <<-RUBY
+ module Core
+ class Engine < ::Rails::Engine; end
+ end
+ RUBY
+ end
+
+ @core.write "db/migrate/1_create_users.rb", <<-RUBY
+ class CreateUsers < ActiveRecord::Migration; end
+ RUBY
+
+ @api.write "db/migrate/2_create_keys.rb", <<-RUBY
+ class CreateKeys < ActiveRecord::Migration; end
+ RUBY
+
+ boot_rails
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake railties:install:migrations`.split("\n")
+
+ assert_match(/Copied migration \d+_create_users.core_engine.rb from core_engine/, output.first)
+ assert_match(/Copied migration \d+_create_keys.api_engine.rb from api_engine/, output.last)
+ end
+ end
+
test "mountable engine should copy migrations within engine_path" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
diff --git a/version.rb b/version.rb
index 672b78c599..4411ec33ef 100644
--- a/version.rb
+++ b/version.rb
@@ -8,7 +8,7 @@ module Rails
MAJOR = 4
MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta2"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end