aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CODE_OF_CONDUCT.md22
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock163
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.md (renamed from RELEASING_RAILS.rdoc)84
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb5
-rw-r--r--actionmailer/test/base_test.rb111
-rw-r--r--actionmailer/test/delivery_methods_test.rb26
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb13
-rw-r--r--actionpack/CHANGELOG.md18
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb59
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb10
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb2
-rw-r--r--actionpack/lib/action_controller/middleware.rb39
-rw-r--r--actionpack/lib/action_controller/test_case.rb33
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb43
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb14
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb65
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb77
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/router/strexp.rb27
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb12
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb127
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb145
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb32
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb87
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb33
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb665
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb117
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/test/abstract_unit.rb61
-rw-r--r--actionpack/test/controller/flash_test.rb2
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb8
-rw-r--r--actionpack/test/controller/integration_test.rb19
-rw-r--r--actionpack/test/controller/new_base/metal_test.rb43
-rw-r--r--actionpack/test/controller/resources_test.rb105
-rw-r--r--actionpack/test/controller/routing_test.rb6
-rw-r--r--actionpack/test/dispatch/exception_wrapper_test.rb32
-rw-r--r--actionpack/test/dispatch/mapper_test.rb103
-rw-r--r--actionpack/test/dispatch/middleware_stack/middleware_test.rb77
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb55
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb11
-rw-r--r--actionpack/test/dispatch/request/session_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb23
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb10
-rw-r--r--actionpack/test/dispatch/static_test.rb2
-rw-r--r--actionpack/test/fixtures/multipart/utf8_filename10
-rw-r--r--actionpack/test/journey/nodes/symbol_test.rb2
-rw-r--r--actionpack/test/journey/path/pattern_test.rb72
-rw-r--r--actionpack/test/journey/route_test.rb27
-rw-r--r--actionpack/test/journey/router_test.rb308
-rw-r--r--actionpack/test/journey/routes_test.rb63
-rw-r--r--actionview/CHANGELOG.md9
-rw-r--r--actionview/lib/action_view/base.rb4
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb29
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb32
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb13
-rw-r--r--actionview/lib/action_view/test_case.rb12
-rw-r--r--actionview/test/abstract_unit.rb14
-rw-r--r--actionview/test/template/form_helper_test.rb18
-rw-r--r--actionview/test/template/form_tag_helper_test.rb40
-rw-r--r--actionview/test/template/test_case_test.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb4
-rw-r--r--activejob/CHANGELOG.md17
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/lib/active_job/core.rb7
-rw-r--r--activejob/lib/active_job/queue_adapters.rb29
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb8
-rw-r--r--activejob/lib/active_job/test_helper.rb13
-rw-r--r--activejob/lib/active_job/translation.rb11
-rw-r--r--activejob/test/cases/job_serialization_test.rb17
-rw-r--r--activejob/test/cases/test_helper_test.rb22
-rw-r--r--activejob/test/cases/translation_test.rb20
-rw-r--r--activejob/test/integration/queuing_test.rb26
-rw-r--r--activejob/test/jobs/translated_hello_job.rb10
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb2
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb6
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb4
-rw-r--r--activemodel/CHANGELOG.md4
-rw-r--r--activemodel/README.rdoc9
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb8
-rw-r--r--activemodel/lib/active_model/serialization.rb3
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb238
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb251
-rw-r--r--activemodel/test/models/contact.rb1
-rw-r--r--activerecord/CHANGELOG.md66
-rw-r--r--activerecord/lib/active_record/associations/association.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb10
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/errors.rb7
-rw-r--r--activerecord/lib/active_record/migration.rb10
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb12
-rw-r--r--activerecord/lib/active_record/relation.rb7
-rw-r--r--activerecord/lib/active_record/relation/batches.rb98
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb67
-rw-r--r--activerecord/lib/active_record/scoping/default.rb1
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb193
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb28
-rw-r--r--activerecord/lib/active_record/type.rb2
-rw-r--r--activerecord/lib/active_record/type/json.rb31
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb172
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb23
-rw-r--r--activerecord/test/cases/associations/eager_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb29
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb28
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb39
-rw-r--r--activerecord/test/cases/attributes_test.rb4
-rw-r--r--activerecord/test/cases/batches_test.rb232
-rw-r--r--activerecord/test/cases/enum_test.rb26
-rw-r--r--activerecord/test/cases/fixtures_test.rb7
-rw-r--r--activerecord/test/cases/migration/postgresql_geometric_types_test.rb93
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb6
-rw-r--r--activerecord/test/cases/serialization_test.rb2
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb12
-rw-r--r--activerecord/test/cases/touch_later_test.rb4
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb23
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb447
-rw-r--r--activerecord/test/fixtures/naked/yml/parrots.yml2
-rw-r--r--activerecord/test/models/carrier.rb2
-rw-r--r--activerecord/test/models/categorization.rb2
-rw-r--r--activerecord/test/models/customer_carrier.rb14
-rw-r--r--activerecord/test/models/member.rb3
-rw-r--r--activerecord/test/models/membership.rb15
-rw-r--r--activerecord/test/models/post.rb8
-rw-r--r--activerecord/test/models/professor.rb5
-rw-r--r--activerecord/test/models/shop_account.rb6
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/models/vehicle.rb7
-rw-r--r--activerecord/test/schema/schema.rb23
-rw-r--r--activerecord/test/support/connection.rb1
-rw-r--r--activesupport/CHANGELOG.md4
-rw-r--r--activesupport/activesupport.gemspec1
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb56
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb4
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb12
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb6
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb18
-rw-r--r--activesupport/test/number_helper_test.rb20
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb25
-rw-r--r--guides/CHANGELOG.md4
-rw-r--r--guides/bug_report_templates/action_controller_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/bug_report_templates/generic_master.rb1
-rw-r--r--guides/source/action_mailer_basics.md5
-rw-r--r--guides/source/active_job_basics.md30
-rw-r--r--guides/source/active_model_basics.md71
-rw-r--r--guides/source/active_record_basics.md4
-rw-r--r--guides/source/active_record_migrations.md31
-rw-r--r--guides/source/active_record_postgresql.md2
-rw-r--r--guides/source/active_support_instrumentation.md8
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/association_basics.md13
-rw-r--r--guides/source/caching_with_rails.md208
-rw-r--r--guides/source/command_line.md8
-rw-r--r--guides/source/configuring.md3
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md3
-rw-r--r--guides/source/debugging_rails_applications.md27
-rw-r--r--guides/source/documents.yaml4
-rw-r--r--guides/source/getting_started.md13
-rw-r--r--guides/source/routing.md18
-rw-r--r--guides/source/security.md40
-rw-r--r--guides/source/testing.md634
-rw-r--r--guides/source/upgrading_ruby_on_rails.md2
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--install.rb16
-rw-r--r--railties/CHANGELOG.md34
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application_controller.rb2
-rw-r--r--railties/lib/rails/commands/server.rb23
-rw-r--r--railties/lib/rails/engine.rb12
-rw-r--r--railties/lib/rails/generators/app_base.rb12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/manifest.js.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb55
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb13
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js11
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb1
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb5
-rw-r--r--railties/lib/rails/mailers_controller.rb2
-rw-r--r--railties/lib/rails/paths.rb8
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb2
-rw-r--r--railties/lib/rails/tasks.rb6
-rw-r--r--railties/lib/rails/tasks/dev.rake15
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb1
-rw-r--r--railties/test/application/asset_debugging_test.rb5
-rw-r--r--railties/test/application/assets_test.rb28
-rw-r--r--railties/test/application/initializers/i18n_test.rb73
-rw-r--r--railties/test/application/rake/dev_test.rb35
-rw-r--r--railties/test/commands/server_test.rb16
-rw-r--r--railties/test/generators/actions_test.rb37
-rw-r--r--railties/test/generators/generators_test_helper.rb2
-rw-r--r--railties/test/generators/plugin_generator_test.rb7
-rw-r--r--railties/test/generators/scaffold_generator_test.rb28
-rw-r--r--railties/test/generators_test.rb24
-rw-r--r--railties/test/paths_test.rb13
266 files changed, 4369 insertions, 3660 deletions
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..65c05c5748
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,22 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic addresses, without explicit permission
+* Other unethical or unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
+
+This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index de4159d254..a398387947 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,7 @@ gem 'rake', '>= 10.3'
# Active Job depends on the URI::GID::MissingModelIDError, which isn't released yet.
gem 'globalid', github: 'rails/globalid'
+gem 'rack', github: 'rack/rack'
# This needs to be with require false as it is
# loaded after loading the test library to
@@ -20,8 +21,9 @@ gem 'turbolinks'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail'
-gem 'sprockets', '~> 3.0.0.rc.1'
+gem 'sprockets', github: 'rails/sprockets', branch: 'master'
gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
+gem 'sass-rails', github: 'rails/sass-rails', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
@@ -31,6 +33,7 @@ gem 'bcrypt', '~> 3.1.10', require: false
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
gem 'uglifier', '>= 1.3.0', require: false
+gem 'sass', '>= 3.3', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
@@ -49,7 +52,7 @@ 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', github: "QueueClassic/queue_classic", require: false, platforms: :ruby
gem 'sneakers', require: false
gem 'que', require: false
gem 'backburner', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 897bdeff83..daaa208a74 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,4 +1,11 @@
GIT
+ remote: git://github.com/QueueClassic/queue_classic.git
+ revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd
+ specs:
+ queue_classic (3.2.0.alpha)
+ pg (>= 0.17, < 0.19)
+
+GIT
remote: git://github.com/bkeepers/qu.git
revision: d098e2657c92e89a6413bebd9c033930759c061f
branch: master
@@ -13,44 +20,70 @@ GIT
GIT
remote: git://github.com/mikel/mail.git
- revision: b159e0a542962fdd5e292a48cfffa560d7cf412e
+ revision: 64ef1a12efcdda53fd63e1456c2c564044bf82ce
specs:
mail (2.6.3.edge)
mime-types (>= 1.16, < 3)
GIT
+ remote: git://github.com/rack/rack.git
+ revision: 2cb9430fce32da2f2dbccf9de97ab691351c1408
+ specs:
+ rack (2.0.0.alpha)
+ json
+
+GIT
remote: git://github.com/rails/arel.git
- revision: aac9da257f291ad8d2d4f914528881c240848bb2
+ revision: d5432b4616ff43fbb14540d351eed351e21bb20e
branch: master
specs:
arel (7.0.0.alpha)
GIT
remote: git://github.com/rails/globalid.git
- revision: 4df66fb9e9f0c832d29119aa8bc30be55a614b71
+ revision: 8178ff2dc898a8f49dd71f6eb46f2a09712462de
specs:
- globalid (0.3.5)
+ globalid (0.3.6)
activesupport (>= 4.1.0)
GIT
remote: git://github.com/rails/jquery-rails.git
- revision: 272abdd319bb3381b23182b928b25320590096b0
+ revision: 38053f45402f1ccc4cce5dd8c7005ec707376db3
branch: master
specs:
- jquery-rails (4.0.3)
+ jquery-rails (4.0.4)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
GIT
+ remote: git://github.com/rails/sass-rails.git
+ revision: 805cb17722b8a13ff00dffe20283a6ba2c9a45dc
+ branch: master
+ specs:
+ sass-rails (6.0.0)
+ railties (>= 4.0.0, < 5.0)
+ sass (~> 3.3)
+ sprockets (>= 4.0)
+ sprockets-rails (< 4.0)
+
+GIT
remote: git://github.com/rails/sprockets-rails.git
- revision: 85b89c44ad40af3056899808475e6e4bf65c1f5a
+ revision: e65e088575be4f6b994823b80a277affb0a57a5e
branch: master
specs:
- sprockets-rails (3.0.0.beta1)
+ sprockets-rails (3.0.0.beta2)
actionpack (>= 4.0)
activesupport (>= 4.0)
- sprockets (>= 3.0.0, < 4.0)
+ sprockets (>= 3.0.0)
+
+GIT
+ remote: git://github.com/rails/sprockets.git
+ revision: 653de5268ba862ea23457291824180c67ad5dc99
+ branch: master
+ specs:
+ sprockets (4.0.0)
+ rack (~> 2.x)
PATH
remote: .
@@ -64,7 +97,7 @@ PATH
actionpack (5.0.0.alpha)
actionview (= 5.0.0.alpha)
activesupport (= 5.0.0.alpha)
- rack (~> 1.6)
+ rack (~> 2.x)
rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
@@ -88,6 +121,7 @@ PATH
concurrent-ruby (~> 0.9.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
+ method_source
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
@@ -112,84 +146,73 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- amq-protocol (1.9.2)
- backburner (0.4.6)
- beaneater (~> 0.3.1)
- dante (~> 0.1.5)
+ amq-protocol (2.0.0)
+ backburner (1.0.0)
+ beaneater (~> 1.0)
+ dante (> 0.1.5)
bcrypt (3.1.10)
bcrypt (3.1.10-x64-mingw32)
bcrypt (3.1.10-x86-mingw32)
- beaneater (0.3.3)
- benchmark-ips (2.1.1)
+ beaneater (1.0.0)
+ benchmark-ips (2.3.0)
builder (3.2.2)
- bunny (1.7.0)
+ bunny (2.0.0)
amq-protocol (>= 1.9.2)
- byebug (4.0.5)
- columnize (= 0.9.0)
+ byebug (6.0.2)
celluloid (0.16.0)
timers (~> 4.0.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
- coffee-script (2.3.0)
+ coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.9.0)
- columnize (0.9.0)
- concurrent-ruby (0.9.0)
- connection_pool (2.1.1)
- dalli (2.7.2)
- dante (0.1.5)
+ coffee-script-source (1.9.1.1)
+ concurrent-ruby (0.9.1)
+ connection_pool (2.2.0)
+ dalli (2.7.4)
+ dante (0.2.0)
delayed_job (4.0.6)
activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.0.3)
activerecord (>= 3.0, < 5.0)
delayed_job (>= 3.0, < 4.1)
erubis (2.7.0)
- execjs (2.3.0)
+ execjs (2.6.0)
hitimes (1.2.2)
hitimes (1.2.2-x86-mingw32)
i18n (0.7.0)
- json (1.8.2)
+ json (1.8.3)
kindlerb (0.1.1)
mustache
nokogiri
- loofah (2.0.1)
+ loofah (2.0.3)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
method_source (0.8.2)
- mime-types (2.4.3)
+ mime-types (2.6.1)
mini_portile (0.6.2)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
- multi_json (1.11.0)
- mustache (1.0.0)
+ multi_json (1.11.2)
+ mustache (1.0.2)
mysql (2.9.1)
- mysql2 (0.3.18)
+ mysql2 (0.3.19)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
- nokogiri (1.6.6.2-x64-mingw32)
- mini_portile (~> 0.6.0)
- nokogiri (1.6.6.2-x86-mingw32)
- mini_portile (~> 0.6.0)
- pg (0.18.1)
+ pg (0.18.2)
psych (2.0.13)
- que (0.9.2)
- queue_classic (3.1.0)
- pg (>= 0.17, < 0.19)
+ que (0.10.0)
racc (1.4.12)
- rack (1.6.0)
rack-cache (1.2)
rack (>= 0.4)
- rack-protection (1.5.3)
- rack
rack-test (0.6.3)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.6)
+ rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
@@ -199,7 +222,7 @@ GEM
rdoc (4.2.0)
redcarpet (3.2.3)
redis (3.2.1)
- redis-namespace (1.5.1)
+ redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
mono_logger (~> 1.0)
@@ -212,47 +235,42 @@ GEM
redis (~> 3.0)
resque (~> 1.25)
rufus-scheduler (~> 3.0)
- rufus-scheduler (3.0.9)
- tzinfo
+ rufus-scheduler (3.1.3)
+ sass (3.4.16)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
- sequel (4.19.0)
+ sequel (4.25.0)
serverengine (1.5.10)
sigdump (~> 0.2.2)
- sidekiq (3.3.2)
- celluloid (>= 0.16.0)
- connection_pool (>= 2.1.1)
- json
- redis (>= 3.0.6)
- redis-namespace (>= 1.3.1)
- sigdump (0.2.2)
- sinatra (1.4.5)
- rack (~> 1.4)
- rack-protection (~> 1.4)
- tilt (~> 1.3, >= 1.3.4)
- sneakers (1.0.4)
- bunny (~> 1.7.0)
+ sidekiq (3.4.2)
+ celluloid (~> 0.16.0)
+ connection_pool (~> 2.2, >= 2.2.0)
+ json (~> 1.0)
+ redis (~> 3.2, >= 3.2.1)
+ redis-namespace (~> 1.5, >= 1.5.2)
+ sigdump (0.2.3)
+ sinatra (1.0)
+ rack (>= 1.0)
+ sneakers (1.1.0)
+ bunny (>= 1.7.0, <= 2.0.0)
serverengine (~> 1.5.5)
thor
thread (~> 0.1.7)
- sprockets (3.0.2)
- rack (~> 1.0)
sqlite3 (1.3.10)
stackprof (0.2.7)
- sucker_punch (1.3.2)
- celluloid (~> 0.16.0)
+ sucker_punch (1.5.1)
+ celluloid (= 0.16.0)
thor (0.19.1)
thread (0.1.7)
thread_safe (0.3.5)
- tilt (1.4.1)
timers (4.0.1)
hitimes
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
- uglifier (2.7.0)
+ uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
vegas (0.1.11)
@@ -294,19 +312,22 @@ DEPENDENCIES
qu-rails!
qu-redis
que
- queue_classic
+ queue_classic!
racc (>= 1.4.6)
+ rack!
rack-cache (~> 1.2)
rails!
rake (>= 10.3)
redcarpet (~> 3.2.3)
resque
resque-scheduler
+ sass (>= 3.3)
+ sass-rails!
sdoc (~> 0.4.0)
sequel
sidekiq
sneakers
- sprockets (~> 3.0.0.rc.1)
+ sprockets!
sprockets-rails!
sqlite3 (~> 1.3.6)
stackprof
@@ -316,4 +337,4 @@ DEPENDENCIES
w3c_validators
BUNDLED WITH
- 1.10.5
+ 1.10.6
diff --git a/README.md b/README.md
index bbe3a6731d..2bc35fa63d 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,8 @@ and may also be used independently outside Rails.
We encourage you to contribute to Ruby on Rails! Please check out the
[Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org)
+Everyone interacting in Rails and its sub-project’s codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](CODE_OF_CONDUCT.md).
+
## Code Status
[![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.md
index a020b958e1..faf8fa7f0d 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.md
@@ -1,43 +1,47 @@
-= Releasing Rails
+# Releasing Rails
In this document, we'll cover the steps necessary to release Rails. Each
section contains steps to take during that time before the release. The times
suggested in each header are just that: suggestions. However, they should
really be considered as minimums.
-== 10 Days before release
+## 10 Days before release
Today is mostly coordination tasks. Here are the things you must do today:
-=== Is the CI green? If not, make it green. (See "Fixing the CI")
+### Is the CI green? If not, make it green. (See "Fixing the CI")
Do not release with a Red CI. You can find the CI status here:
- http://travis-ci.org/rails/rails
+```
+http://travis-ci.org/rails/rails
+```
-=== Is Sam Ruby happy? If not, make him happy.
+### Is Sam Ruby happy? If not, make him happy.
Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
Web Development with Rails) all work. These are valuable integration tests
for Rails. You can check the status of his tests here:
- http://intertwingly.net/projects/dashboard.html
+```
+http://intertwingly.net/projects/dashboard.html
+```
Do not release with Red AWDwR tests.
-=== Do we have any Git dependencies? If so, contact those authors.
+### Do we have any Git dependencies? If so, contact those authors.
Having Git dependencies indicates that we depend on unreleased code.
Obviously Rails cannot be released when it depends on unreleased code.
Contact the authors of those particular gems and work out a release date that
suits them.
-=== Contact the security team (either Koz or tenderlove)
+### Contact the security team (either Koz or tenderlove)
Let them know of your plans to release. There may be security issues to be
addressed, and that can impact your release date.
-=== Notify implementors.
+### Notify implementors.
Ruby implementors have high stakes in making sure Rails works. Be kind and
give them a heads up that Rails will be released soonish.
@@ -54,27 +58,29 @@ lists:
Implementors will love you and help you.
-== 3 Days before release
+### 3 Days before release
This is when you should release the release candidate. Here are your tasks
for today:
-=== Is the CI green? If not, make it green.
+### Is the CI green? If not, make it green.
-=== Is Sam Ruby happy? If not, make him happy.
+### Is Sam Ruby happy? If not, make him happy.
-=== Contact the security team. CVE emails must be sent on this day.
+### Contact the security team. CVE emails must be sent on this day.
-=== Create a release branch.
+### Create a release branch.
From the stable branch, create a release branch. For example, if you're
releasing Rails 3.0.10, do this:
- [aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
- Switched to a new branch '3-0-10'
- [aaron@higgins rails (3-0-10)]$
+```
+[aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
+Switched to a new branch '3-0-10'
+[aaron@higgins rails (3-0-10)]$
+```
-=== Update each CHANGELOG.
+### Update each CHANGELOG.
Many times commits are made without the CHANGELOG being updated. You should
review the commits since the last release, and fill in any missing information
@@ -82,15 +88,17 @@ for each CHANGELOG.
You can review the commits for the 3.0.10 release like this:
- [aaron@higgins rails (3-0-10)]$ git log v3.0.9..
+```
+[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
+```
If you're doing a stable branch release, you should also ensure that all of
the CHANGELOG entries in the stable branch are also synced to the master
branch.
-=== Update the RAILS_VERSION file to include the RC.
+### Update the RAILS_VERSION file to include the RC.
-=== Build and test the gem.
+### Build and test the gem.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
@@ -98,7 +106,7 @@ generating a new app and ensure that nothing explodes.
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
-=== Release the gem.
+### Release the gem.
IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
to use Ruby 1.8 when releasing.
@@ -108,14 +116,16 @@ RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
Here are the commands that `rake release` should use, so you can understand
what to do in case anything goes wrong:
- $ rake all:build
- $ git commit -am'updating RAILS_VERSION'
- $ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
- $ git push
- $ git push --tags
- $ for i in $(ls pkg); do gem push $i; done
+```
+$ rake all:build
+$ git commit -am'updating RAILS_VERSION'
+$ git tag -m 'v3.0.10.rc1 release' v3.0.10.rc1
+$ git push
+$ git push --tags
+$ for i in $(ls pkg); do gem push $i; done
+```
-=== Send Rails release announcements
+### Send Rails release announcements
Write a release announcement that includes the version, changes, and links to
GitHub where people can find the specific commit list. Here are the mailing
@@ -132,16 +142,16 @@ IMPORTANT: If any users experience regressions when using the release
candidate, you *must* postpone the release. Bugfix releases *should not*
break existing applications.
-=== Post the announcement to the Rails blog.
+### Post the announcement to the Rails blog.
If you used Markdown format for your email, you can just paste it in to the
blog.
* http://weblog.rubyonrails.org
-=== Post the announcement to the Rails Twitter account.
+### Post the announcement to the Rails Twitter account.
-== Time between release candidate and actual release
+## Time between release candidate and actual release
Check the rails-core mailing list and the GitHub issue list for regressions in
the RC.
@@ -155,7 +165,7 @@ When you fix the regressions, do not create a new branch. Fix them on the
stable branch, then cherry pick the commit to your release branch. No other
commits should be added to the release branch besides regression fixing commits.
-== Day of release
+## Day of release
Many of these steps are the same as for the release candidate, so if you need
more explanation on a particular step, see the RC steps.
@@ -173,7 +183,7 @@ Today, do this stuff in this order:
* Email security lists
* Email general announcement lists
-=== Emailing the Rails security announce list
+### Emailing the Rails security announce list
Email the security announce list once for each vulnerability fixed.
@@ -193,13 +203,13 @@ so we need to give them the security fixes in patch form.
* Merge the release branch to the stable branch.
* Drink beer (or other cocktail)
-== Misc
+## Misc
-=== Fixing the CI
+### Fixing the CI
There are two simple steps for fixing the CI:
1. Identify the problem
2. Fix it
-Repeat these steps until the CI is green.
+Repeat these steps until the CI is green. \ No newline at end of file
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 7022c04a9a..6f49e130d8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -820,7 +820,7 @@ module ActionMailer
# Set configure delivery behavior
wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
- # Assign all headers except parts_order, content_type and body
+ # Assign all headers except parts_order, content_type, body, template_name, and template_path
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
assignable.each { |k, v| m[k] = v }
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 706249a2f6..85d3629514 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -9,6 +9,7 @@ silence_warnings do
end
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
require 'action_mailer'
require 'action_mailer/test_case'
@@ -40,4 +41,6 @@ def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
-require 'mocha/setup' # FIXME: stop using mocha
+class ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 59c5638f96..6b2e6a531c 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -505,9 +505,10 @@ class BaseTest < ActiveSupport::TestCase
end
test "calling deliver on the action should deliver the mail object" do
- BaseMailer.expects(:deliver_mail).once
- mail = BaseMailer.welcome.deliver_now
- assert_equal 'The first email on new API!', mail.subject
+ assert_called(BaseMailer, :deliver_mail) do
+ mail = BaseMailer.welcome.deliver_now
+ assert_equal 'The first email on new API!', mail.subject
+ end
end
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
@@ -517,9 +518,11 @@ class BaseTest < ActiveSupport::TestCase
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
mail = Mail::Message.new
- mail.expects(:do_delivery).once
- BaseMailer.expects(:welcome).returns(mail)
- BaseMailer.welcome.deliver
+ assert_called(mail, :do_delivery) do
+ assert_called(BaseMailer, :welcome, returns: mail) do
+ BaseMailer.welcome.deliver
+ end
+ end
end
# Rendering
@@ -607,8 +610,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_observer(MyObserver)
mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyObserver, :delivered_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -616,8 +620,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_observer("BaseTest::MyObserver")
mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyObserver, :delivered_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -625,8 +630,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_observer(:"base_test/my_observer")
mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyObserver, :delivered_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -634,9 +640,11 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- MySecondObserver.expects(:delivered_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyObserver, :delivered_email, [mail]) do
+ assert_called_with(MySecondObserver, :delivered_email, [mail]) do
+ mail.deliver_now
+ end
+ end
end
end
@@ -654,8 +662,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_interceptor(MyInterceptor)
mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyInterceptor, :delivering_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -663,8 +672,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyInterceptor, :delivering_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -672,8 +682,9 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyInterceptor, :delivering_email, [mail]) do
+ mail.deliver_now
+ end
end
end
@@ -681,18 +692,21 @@ class BaseTest < ActiveSupport::TestCase
mail_side_effects do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- MySecondInterceptor.expects(:delivering_email).with(mail)
- mail.deliver_now
+ assert_called_with(MyInterceptor, :delivering_email, [mail]) do
+ assert_called_with(MySecondInterceptor, :delivering_email, [mail]) do
+ mail.deliver_now
+ end
+ end
end
end
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome['X-Proc-Method']
yesterday = 1.day.ago
- Time.stubs(:now).returns(yesterday)
- mail2 = ProcMailer.welcome['X-Proc-Method']
- assert(mail1.to_s.to_i > mail2.to_s.to_i)
+ Time.stub(:now, yesterday) do
+ mail2 = ProcMailer.welcome['X-Proc-Method']
+ assert(mail1.to_s.to_i > mail2.to_s.to_i)
+ end
end
test 'default values which have to_proc (e.g. symbols) should not be considered procs' do
@@ -877,33 +891,50 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(MyInterceptor)
mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
+ stub_any_instance(BaseMailerPreview) do |instance|
+ instance.stub(:welcome, mail) do
+ assert_called_with(MyInterceptor, :previewing_email, [mail]) do
+ BaseMailerPreview.call(:welcome)
+ end
+ end
+ end
end
test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
+ stub_any_instance(BaseMailerPreview) do |instance|
+ instance.stub(:welcome, mail) do
+ assert_called_with(MyInterceptor, :previewing_email, [mail]) do
+ BaseMailerPreview.call(:welcome)
+ end
+ end
+ end
end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
+ stub_any_instance(BaseMailerPreview) do |instance|
+ instance.stub(:welcome, mail) do
+ assert_called_with(MyInterceptor, :previewing_email, [mail]) do
+ BaseMailerPreview.call(:welcome)
+ end
+ end
+ end
end
test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- MySecondInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
+ stub_any_instance(BaseMailerPreview) do |instance|
+ instance.stub(:welcome, mail) do
+ assert_called_with(MyInterceptor, :previewing_email, [mail]) do
+ assert_called_with(MySecondInterceptor, :previewing_email, [mail]) do
+ BaseMailerPreview.call(:welcome)
+ end
+ end
+ end
+ end
end
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 78507ce7dc..ceaa4ca83e 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -102,16 +102,21 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "ActionMailer should be told when Mail gets delivered" do
- DeliveryMailer.expects(:deliver_mail).once
- DeliveryMailer.welcome.deliver_now
+ DeliveryMailer.delivery_method = :test
+ assert_called(DeliveryMailer, :deliver_mail) do
+ DeliveryMailer.welcome.deliver_now
+ end
end
test "delivery method can be customized per instance" do
- Mail::SMTP.any_instance.expects(:deliver!)
- email = DeliveryMailer.welcome.deliver_now
- assert_instance_of Mail::SMTP, email.delivery_method
- email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
- assert_instance_of Mail::TestMailer, email.delivery_method
+ stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
+ assert_called(instance, :deliver!) do
+ email = DeliveryMailer.welcome.deliver_now
+ assert_instance_of Mail::SMTP, email.delivery_method
+ email = DeliveryMailer.welcome(delivery_method: :test).deliver_now
+ assert_instance_of Mail::TestMailer, email.delivery_method
+ end
+ end
end
test "delivery method can be customized in subclasses not changing the parent" do
@@ -176,8 +181,11 @@ class MailDeliveryTest < ActiveSupport::TestCase
old_perform_deliveries = DeliveryMailer.perform_deliveries
begin
DeliveryMailer.perform_deliveries = false
- Mail::Message.any_instance.expects(:deliver!).never
- DeliveryMailer.welcome.deliver_now
+ stub_any_instance(Mail::Message) do |instance|
+ assert_not_called(instance, :deliver!) do
+ DeliveryMailer.welcome.deliver_now
+ end
+ end
ensure
DeliveryMailer.perform_deliveries = old_perform_deliveries
end
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 5ffde06a80..04e00cf481 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -53,12 +53,15 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
end
def test_send_mail
- Mail::SMTP.any_instance.expects(:deliver!)
- with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
- ActiveSupport::Deprecation.silence do
- get '/test/send_mail'
+ stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
+ assert_called(instance, :deliver!) do
+ with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
+ ActiveSupport::Deprecation.silence do
+ get '/test/send_mail'
+ end
+ assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
+ end
end
- assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 6a68c057ac..8eea4ccd41 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,21 @@
+* Using strings or symbols for middleware class names is deprecated. Convert
+ things like this:
+
+ middleware.use "Foo::Bar"
+
+ to this:
+
+ middleware.use Foo::Bar
+
+* ActionController::TestSession now accepts a default value as well as
+ a block for generating a default value based off the key provided.
+
+ This fixes calls to session#fetch in ApplicationController instances that
+ take more two arguments or a block from raising `ArgumentError: wrong
+ number of arguments (2 for 1)` when performing controller tests.
+
+ *Matthew Gerrior*
+
* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
default block.
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 1bba9df969..28d8bc3091 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'rack', '~> 1.6'
+ s.add_dependency 'rack', '~> 2.x'
s.add_dependency 'rack-test', '~> 0.6.3'
s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2'
s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 5514213ad8..887196b3d2 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -54,11 +54,11 @@ module AbstractController
Mime::TEXT
end
- DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w(
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i(
@_action_name @_response_body @_formats @_prefixes @_config
@_view_context_class @_view_renderer @_lookup_context
@_routes @_db_runtime
- ).map(&:to_sym)
+ )
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 17371a5392..55734b9774 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -252,7 +252,7 @@ module ActionController
# Define some internal variables that should not be propagated to the view.
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_status, :@_headers, :@_params, :@_response, :@_request,
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
def _protected_ivars # :nodoc:
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index ae111e4951..914b0d4b30 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
+require 'active_support/deprecation'
module ActionController
# Extend ActionDispatch middleware stack to make it aware of options
@@ -11,22 +12,14 @@ module ActionController
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
- def initialize(klass, *args, &block)
- options = args.extract_options!
- @only = Array(options.delete(:only)).map(&:to_s)
- @except = Array(options.delete(:except)).map(&:to_s)
- args << options unless options.empty?
- super
+ def initialize(klass, args, actions, strategy, block)
+ @actions = actions
+ @strategy = strategy
+ super(klass, args, block)
end
def valid?(action)
- if @only.present?
- @only.include?(action)
- elsif @except.present?
- !@except.include?(action)
- else
- true
- end
+ @strategy.call @actions, action
end
end
@@ -37,6 +30,32 @@ module ActionController
middleware.valid?(action) ? middleware.build(a) : a
end
end
+
+ private
+
+ INCLUDE = ->(list, action) { list.include? action }
+ EXCLUDE = ->(list, action) { !list.include? action }
+ NULL = ->(list, action) { true }
+
+ def build_middleware(klass, args, block)
+ options = args.extract_options!
+ only = Array(options.delete(:only)).map(&:to_s)
+ except = Array(options.delete(:except)).map(&:to_s)
+ args << options unless options.empty?
+
+ strategy = NULL
+ list = nil
+
+ if only.any?
+ strategy = INCLUDE
+ list = only
+ elsif except.any?
+ strategy = EXCLUDE
+ list = except
+ end
+
+ Middleware.new(get_class(klass), args, list, strategy, block)
+ end
end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@@ -98,11 +117,10 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- attr_internal_writer :env
-
def env
- @_env ||= {}
+ @_request.env
end
+ deprecate :env
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
@@ -197,8 +215,7 @@ module ActionController
def set_request!(request) #:nodoc:
@_request = request
- @_env = request.env
- @_env['action_controller.instance'] = self
+ @_request.controller_instance = self
end
def to_a #:nodoc:
@@ -232,13 +249,13 @@ module ActionController
end
# Returns a Rack endpoint for the given action name.
- def self.action(name, klass = ActionDispatch::Request)
+ def self.action(name)
if middleware_stack.any?
middleware_stack.build(name) do |env|
- new.dispatch(name, klass.new(env))
+ new.dispatch(name, ActionDispatch::Request.new(env))
end
else
- lambda { |env| new.dispatch(name, klass.new(env)) }
+ lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) }
end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 032275ac64..bbb38cf8fc 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -94,7 +94,7 @@ module ActionController
end
def has_basic_credentials?(request)
- request.authorization.present? && (auth_scheme(request) == 'Basic')
+ request.authorization.present? && (auth_scheme(request).downcase == 'basic')
end
def user_name_and_password(request)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 9e09242872..d21a778d8d 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -139,7 +139,7 @@ module ActionController #:nodoc:
request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
+ request.cookie_jar = NullCookieJar.build(request, {})
end
protected
@@ -160,14 +160,6 @@ module ActionController #:nodoc:
end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def self.build(request)
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
- host = request.host
- secure = request.ssl?
-
- new(key_generator, host, secure, options_for_env({}))
- end
-
def write(*)
# nothing
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index af31de1f3a..a6115674aa 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
def _process_options(options) #:nodoc:
super
if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
+ if request.version == "HTTP/1.0"
options.delete(:stream)
else
headers["Cache-Control"] ||= "no-cache"
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index e78f1f0d7e..fc8e345d43 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -461,7 +461,7 @@ module ActionController
end
end
- # Performs keys transfomration and returns the altered
+ # Performs keys transformation and returns the altered
# <tt>ActionController::Parameters</tt> instance.
def transform_keys!(&block)
@parameters.transform_keys!(&block)
diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb
deleted file mode 100644
index 437fec3dc6..0000000000
--- a/actionpack/lib/action_controller/middleware.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module ActionController
- class Middleware < Metal
- class ActionMiddleware
- def initialize(controller, app)
- @controller, @app = controller, app
- end
-
- def call(env)
- request = ActionDispatch::Request.new(env)
- @controller.build(@app).dispatch(:index, request)
- end
- end
-
- class << self
- alias build new
-
- def new(app)
- ActionMiddleware.new(self, app)
- end
- end
-
- attr_internal :app
-
- def process(action)
- response = super
- self.status, self.headers, self.response_body = response if response.is_a?(Array)
- response
- end
-
- def initialize(app)
- super()
- @_app = app
- end
-
- def index
- call(env)
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index f922e134f7..e012fa617e 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,4 +1,5 @@
require 'rack/session/abstract/id'
+require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
@@ -42,14 +43,12 @@ module ActionController
@env["action_dispatch.request.request_parameters"] = params
end
- def assign_parameters(routes, controller_path, action, parameters = {})
- parameters = parameters.symbolize_keys
- generated_path, extra_keys = routes.generate_extras(parameters.merge(:controller => controller_path, :action => action))
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
non_path_parameters = {}
path_parameters = {}
parameters.each do |key, value|
- if extra_keys.include?(key) || key == :action || key == :controller
+ if query_string_keys.include?(key)
non_path_parameters[key] = value
else
if value.is_a?(Array)
@@ -171,6 +170,10 @@ module ActionController
clear
end
+ def fetch(*args, &block)
+ @data.fetch(*args, &block)
+ end
+
private
def load!
@@ -479,11 +482,13 @@ module ActionController
@request.env['REQUEST_METHOD'] = http_method
- controller_class_name = @controller.class.anonymous? ?
- "anonymous" :
- @controller.class.controller_path
+ parameters = parameters.symbolize_keys
+
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
+ generated_path = generated_path(generated_extras)
+ query_string_keys = query_parameter_names(generated_extras)
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
+ @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
@request.session.update(session) if session
@request.flash.update(flash || {})
@@ -526,6 +531,18 @@ module ActionController
@response
end
+ def controller_class_name
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
+ end
+
+ def generated_path(generated_extras)
+ generated_extras[0]
+ end
+
+ def query_parameter_names(generated_extras)
+ generated_extras[1] + [:controller, :action]
+ end
+
def setup_controller_request_and_response
@controller = nil unless defined? @controller
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
index bf79963351..94c1f2b41f 100644
--- a/actionpack/lib/action_dispatch/http/filter_redirect.rb
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -5,8 +5,7 @@ module ActionDispatch
FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location # :nodoc:
- filters = location_filter
- if !filters.empty? && location_filter_match?(filters)
+ if location_filter_match?
FILTERED
else
location
@@ -15,7 +14,7 @@ module ActionDispatch
private
- def location_filter
+ def location_filters
if request
request.env['action_dispatch.redirect_filter'] || []
else
@@ -23,12 +22,12 @@ module ActionDispatch
end
end
- def location_filter_match?(filters)
- filters.any? do |filter|
+ def location_filter_match?
+ location_filters.any? do |filter|
if String === filter
location.include?(filter)
elsif Regexp === filter
- location.match(filter)
+ location =~ filter
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 6985cec5f5..de28cd0998 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -34,12 +34,14 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
+ HTTP_X_FORWARDED_FOR HTTP_VERSION
+ ].freeze
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
- @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
+ @env["#{env}".freeze] # @env["HTTP_ACCEPT_CHARSET".freeze]
end # end
METHOD
end
@@ -103,13 +105,17 @@ module ActionDispatch
# the application should use), this \method returns the overridden
# value, not the original.
def request_method
- @request_method ||= check_method(env["REQUEST_METHOD"])
+ @request_method ||= check_method(super)
end
def routes # :nodoc:
env["action_dispatch.routes".freeze]
end
+ def routes=(routes) # :nodoc:
+ env["action_dispatch.routes".freeze] = routes
+ end
+
def original_script_name # :nodoc:
env['ORIGINAL_SCRIPT_NAME'.freeze]
end
@@ -118,12 +124,31 @@ module ActionDispatch
env[_routes.env_key]
end
+ def engine_script_name=(name) # :nodoc:
+ env[routes.env_key] = name.dup
+ end
+
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
end
end
+ def controller_instance # :nodoc:
+ env['action_controller.instance'.freeze]
+ end
+
+ def controller_instance=(controller) # :nodoc:
+ env['action_controller.instance'.freeze] = controller
+ end
+
+ def show_exceptions? # :nodoc:
+ # We're treating `nil` as "unset", and we want the default setting to be
+ # `true`. This logic should be extracted to `env_config` and calculated
+ # once.
+ !(env['action_dispatch.show_exceptions'.freeze] == false)
+ end
+
# Returns a symbol form of the #request_method
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
@@ -210,6 +235,10 @@ module ActionDispatch
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ def remote_ip=(remote_ip)
+ @env["action_dispatch.remote_ip".freeze] = remote_ip
+ end
+
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
# Returns the unique request id, which is based on either the X-Request-Id header that can
@@ -318,6 +347,14 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
+ def request_parameters=(params)
+ env["action_dispatch.request.request_parameters".freeze] = params
+ end
+
+ def logger
+ env["action_dispatch.logger".freeze]
+ end
+
private
def check_method(name)
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 540e11a4a0..a221f4c5af 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -28,7 +28,13 @@ module ActionDispatch
raise(ArgumentError, ':tempfile is required') unless @tempfile
@original_filename = hash[:filename]
- @original_filename &&= @original_filename.encode "UTF-8"
+ if @original_filename
+ begin
+ @original_filename.encode!(Encoding::UTF_8)
+ rescue EncodingError
+ @original_filename.force_encoding(Encoding::UTF_8)
+ end
+ end
@content_type = hash[:type]
@headers = hash[:head]
end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index d8bb10ffab..c19ff0f4db 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -155,7 +155,7 @@ module ActionDispatch
def build_cache
root = { ___routes: [] }
- routes.each_with_index do |route, i|
+ routes.routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
end
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index cf6542b370..d069bf0205 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -14,15 +14,15 @@ module ActionDispatch
end
def each(&block)
- Visitors::Each.new(block).accept(self)
+ Visitors::Each::INSTANCE.accept(self, block)
end
def to_s
- Visitors::String.new.accept(self)
+ Visitors::String::INSTANCE.accept(self, '')
end
def to_dot
- Visitors::Dot.new.accept(self)
+ Visitors::Dot::INSTANCE.accept(self)
end
def to_sym
@@ -39,10 +39,14 @@ module ActionDispatch
def symbol?; false; end
def literal?; false; end
+ def terminal?; false; end
+ def star?; false; end
+ def cat?; false; end
end
class Terminal < Node # :nodoc:
alias :symbol :left
+ def terminal?; true; end
end
class Literal < Terminal # :nodoc:
@@ -69,11 +73,13 @@ module ActionDispatch
class Symbol < Terminal # :nodoc:
attr_accessor :regexp
alias :symbol :regexp
+ attr_reader :name
DEFAULT_EXP = /[^\.\/\?]+/
def initialize(left)
super
@regexp = DEFAULT_EXP
+ @name = left.tr '*:'.freeze, ''.freeze
end
def default_regexp?
@@ -92,6 +98,7 @@ module ActionDispatch
end
class Star < Unary # :nodoc:
+ def star?; true; end
def type; :STAR; end
def name
@@ -111,6 +118,7 @@ module ActionDispatch
end
class Cat < Binary # :nodoc:
+ def cat?; true; end
def type; :CAT; end
end
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
index 14892f4321..fff0299812 100644
--- a/actionpack/lib/action_dispatch/journey/parser_extras.rb
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -6,6 +6,10 @@ module ActionDispatch
class Parser < Racc::Parser # :nodoc:
include Journey::Nodes
+ def self.parse(string)
+ new.parse string
+ end
+
def initialize
@scanner = Scanner.new
end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 64b48ca45f..e93970046c 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -1,5 +1,3 @@
-require 'action_dispatch/journey/router/strexp'
-
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
@@ -7,14 +5,20 @@ module ActionDispatch
attr_reader :spec, :requirements, :anchored
def self.from_string string
- new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
+ build(string, {}, "/.?", true)
+ end
+
+ def self.build(path, requirements, separators, anchored)
+ parser = Journey::Parser.new
+ ast = parser.parse path
+ new ast, requirements, separators, anchored
end
- def initialize(strexp)
- @spec = strexp.ast
- @requirements = strexp.requirements
- @separators = strexp.separators.join
- @anchored = strexp.anchor
+ def initialize(ast, requirements, separators, anchored)
+ @spec = ast
+ @requirements = requirements
+ @separators = separators
+ @anchored = anchored
@names = nil
@optional_names = nil
@@ -28,12 +32,12 @@ module ActionDispatch
end
def ast
- @spec.grep(Nodes::Symbol).each do |node|
+ @spec.find_all(&:symbol?).each do |node|
re = @requirements[node.to_sym]
node.regexp = re if re
end
- @spec.grep(Nodes::Star).each do |node|
+ @spec.find_all(&:star?).each do |node|
node = node.left
node.regexp = @requirements[node.to_sym] || /(.+)/
end
@@ -55,31 +59,6 @@ module ActionDispatch
}.map(&:name).uniq
end
- class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
- attr_reader :offsets
-
- def initialize(matchers)
- @matchers = matchers
- @capture_count = [0]
- end
-
- def visit(node)
- super
- @capture_count
- end
-
- def visit_SYMBOL(node)
- node = node.to_sym
-
- if @matchers.key?(node)
- re = /#{@matchers[node]}|/
- @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
- else
- @capture_count << (@capture_count.last || 0)
- end
- end
- end
-
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
def initialize(separator, matchers)
@separator = separator
@@ -189,8 +168,20 @@ module ActionDispatch
def offsets
return @offsets if @offsets
- viz = RegexpOffsets.new(@requirements)
- @offsets = viz.accept(spec)
+ @offsets = [0]
+
+ spec.find_all(&:symbol?).each do |node|
+ node = node.to_sym
+
+ if @requirements.key?(node)
+ re = /#{@requirements[node]}|/
+ @offsets.push((re.match('').length - 1) + @offsets.last)
+ else
+ @offsets << @offsets.last
+ end
+ end
+
+ @offsets
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index cbc985640a..f5c9abf1cc 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -1,36 +1,81 @@
module ActionDispatch
module Journey # :nodoc:
class Route # :nodoc:
- attr_reader :app, :path, :defaults, :name
+ attr_reader :app, :path, :defaults, :name, :precedence
attr_reader :constraints
alias :conditions :constraints
- attr_accessor :precedence
+ module VerbMatchers
+ VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
+ VERBS.each do |v|
+ class_eval <<-eoc
+ class #{v}
+ def self.verb; name.split("::").last; end
+ def self.call(req); req.#{v.downcase}?; end
+ end
+ eoc
+ end
+
+ class Unknown
+ attr_reader :verb
+
+ def initialize(verb)
+ @verb = verb
+ end
+
+ def call(request); @verb === request.request_method; end
+ end
+
+ class All
+ def self.call(_); true; end
+ def self.verb; ''; end
+ end
+
+ VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
+ klass = const_get verb
+ hash[verb] = klass
+ hash[verb.downcase] = klass
+ hash[verb.downcase.to_sym] = klass
+ end
+
+ end
+
+ def self.verb_matcher(verb)
+ VerbMatchers::VERB_TO_CLASS.fetch(verb) do
+ VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
+ end
+ end
+
+ def self.build(name, app, path, constraints, required_defaults, defaults)
+ request_method_match = verb_matcher(constraints.delete(:request_method))
+ new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
+ end
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
- def initialize(name, app, path, constraints, required_defaults, defaults)
+ def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence)
@name = name
@app = app
@path = path
+ @request_method_match = request_method_match
@constraints = constraints
@defaults = defaults
@required_defaults = nil
- @_required_defaults = required_defaults || []
+ @_required_defaults = required_defaults
@required_parts = nil
@parts = nil
@decorated_ast = nil
- @precedence = 0
+ @precedence = precedence
@path_formatter = @path.build_formatter
end
def ast
@decorated_ast ||= begin
decorated_ast = path.ast
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
+ decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
decorated_ast
end
end
@@ -92,7 +137,8 @@ module ActionDispatch
end
def matches?(request)
- constraints.all? do |method, value|
+ match_verb(request) &&
+ constraints.all? { |method, value|
case value
when Regexp, String
value === request.send(method).to_s
@@ -105,15 +151,28 @@ module ActionDispatch
else
value === request.send(method)
end
- end
+ }
end
def ip
constraints[:ip] || //
end
+ def requires_matching_verb?
+ !@request_method_match.all? { |x| x == VerbMatchers::All }
+ end
+
def verb
- constraints[:request_method] || //
+ %r[^#{verbs.join('|')}$]
+ end
+
+ private
+ def verbs
+ @request_method_match.map(&:verb)
+ end
+
+ def match_verb(request)
+ @request_method_match.any? { |m| m.call request }
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index b84aad8eb6..f649588520 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -1,5 +1,4 @@
require 'action_dispatch/journey/router/utils'
-require 'action_dispatch/journey/router/strexp'
require 'action_dispatch/journey/routes'
require 'action_dispatch/journey/formatter'
@@ -102,7 +101,7 @@ module ActionDispatch
}
routes =
- if req.request_method == "HEAD"
+ if req.head?
match_head_routes(routes, req)
else
match_routes(routes, req)
@@ -121,7 +120,7 @@ module ActionDispatch
end
def match_head_routes(routes, req)
- verb_specific_routes = routes.reject { |route| route.verb == // }
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
head_routes = match_routes(verb_specific_routes, req)
if head_routes.empty?
diff --git a/actionpack/lib/action_dispatch/journey/router/strexp.rb b/actionpack/lib/action_dispatch/journey/router/strexp.rb
deleted file mode 100644
index 4b7738f335..0000000000
--- a/actionpack/lib/action_dispatch/journey/router/strexp.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module ActionDispatch
- module Journey # :nodoc:
- class Router # :nodoc:
- class Strexp # :nodoc:
- class << self
- alias :compile :new
- end
-
- attr_reader :path, :requirements, :separators, :anchor, :ast
-
- def self.build(path, requirements, separators, anchor = true)
- parser = Journey::Parser.new
- ast = parser.parse path
- new ast, path, requirements, separators, anchor
- end
-
- def initialize(ast, path, requirements, separators, anchor = true)
- @ast = ast
- @path = path
- @requirements = requirements
- @separators = separators
- @anchor = anchor
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index 5990964b57..f7b009109e 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -5,11 +5,10 @@ module ActionDispatch
class Routes # :nodoc:
include Enumerable
- attr_reader :routes, :named_routes, :custom_routes, :anchored_routes
+ attr_reader :routes, :custom_routes, :anchored_routes
def initialize
@routes = []
- @named_routes = {}
@ast = nil
@anchored_routes = []
@custom_routes = []
@@ -37,7 +36,6 @@ module ActionDispatch
routes.clear
anchored_routes.clear
custom_routes.clear
- named_routes.clear
end
def partition_route(route)
@@ -62,13 +60,9 @@ module ActionDispatch
end
end
- # Add a route to the routing table.
- def add_route(app, path, conditions, required_defaults, defaults, name = nil)
- route = Route.new(name, app, path, conditions, required_defaults, defaults)
-
- route.precedence = routes.length
+ def add_route(name, mapping)
+ route = mapping.make_route name, routes.length
routes << route
- named_routes[name] = route if name && !named_routes[name]
partition_route(route)
clear_cache!
route
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 52b4c8b489..537c9b2f5c 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -92,6 +92,45 @@ module ActionDispatch
end
end
+ class FunctionalVisitor # :nodoc:
+ DISPATCH_CACHE = {}
+
+ def accept(node, seed)
+ visit(node, seed)
+ end
+
+ def visit node, seed
+ send(DISPATCH_CACHE[node.type], node, seed)
+ end
+
+ def binary(node, seed)
+ visit(node.right, visit(node.left, seed))
+ end
+ def visit_CAT(n, seed); binary(n, seed); end
+
+ def nary(node, seed)
+ node.children.inject(seed) { |s, c| visit(c, s) }
+ end
+ def visit_OR(n, seed); nary(n, seed); end
+
+ def unary(node, seed)
+ visit(node.left, seed)
+ end
+ def visit_GROUP(n, seed); unary(n, seed); end
+ def visit_STAR(n, seed); unary(n, seed); end
+
+ def terminal(node, seed); seed; end
+ def visit_LITERAL(n, seed); terminal(n, seed); end
+ def visit_SYMBOL(n, seed); terminal(n, seed); end
+ def visit_SLASH(n, seed); terminal(n, seed); end
+ def visit_DOT(n, seed); terminal(n, seed); end
+
+ instance_methods(false).each do |pim|
+ next unless pim =~ /^visit_(.*)$/
+ DISPATCH_CACHE[$1.to_sym] = pim
+ end
+ end
+
class FormatBuilder < Visitor # :nodoc:
def accept(node); Journey::Format.new(super); end
def terminal(node); [node.left]; end
@@ -117,104 +156,110 @@ module ActionDispatch
end
# Loop through the requirements AST
- class Each < Visitor # :nodoc:
- attr_reader :block
-
- def initialize(block)
- @block = block
- end
-
- def visit(node)
+ class Each < FunctionalVisitor # :nodoc:
+ def visit(node, block)
block.call(node)
super
end
+
+ INSTANCE = new
end
- class String < Visitor # :nodoc:
+ class String < FunctionalVisitor # :nodoc:
private
- def binary(node)
- [visit(node.left), visit(node.right)].join
+ def binary(node, seed)
+ visit(node.right, visit(node.left, seed))
end
- def nary(node)
- node.children.map { |c| visit(c) }.join '|'
+ def nary(node, seed)
+ last_child = node.children.last
+ node.children.inject(seed) { |s, c|
+ string = visit(c, s)
+ string << "|".freeze unless last_child == c
+ string
+ }
end
- def terminal(node)
- node.left
+ def terminal(node, seed)
+ seed + node.left
end
- def visit_GROUP(node)
- "(#{visit(node.left)})"
+ def visit_GROUP(node, seed)
+ visit(node.left, seed << "(".freeze) << ")".freeze
end
+
+ INSTANCE = new
end
- class Dot < Visitor # :nodoc:
+ class Dot < FunctionalVisitor # :nodoc:
def initialize
@nodes = []
@edges = []
end
- def accept(node)
+ def accept(node, seed = [[], []])
super
+ nodes, edges = seed
<<-eodot
digraph parse_tree {
size="8,5"
node [shape = none];
edge [dir = none];
- #{@nodes.join "\n"}
- #{@edges.join("\n")}
+ #{nodes.join "\n"}
+ #{edges.join("\n")}
}
eodot
end
private
- def binary(node)
- node.children.each do |c|
- @edges << "#{node.object_id} -> #{c.object_id};"
- end
+ def binary(node, seed)
+ seed.last.concat node.children.map { |c|
+ "#{node.object_id} -> #{c.object_id};"
+ }
super
end
- def nary(node)
- node.children.each do |c|
- @edges << "#{node.object_id} -> #{c.object_id};"
- end
+ def nary(node, seed)
+ seed.last.concat node.children.map { |c|
+ "#{node.object_id} -> #{c.object_id};"
+ }
super
end
- def unary(node)
- @edges << "#{node.object_id} -> #{node.left.object_id};"
+ def unary(node, seed)
+ seed.last << "#{node.object_id} -> #{node.left.object_id};"
super
end
- def visit_GROUP(node)
- @nodes << "#{node.object_id} [label=\"()\"];"
+ def visit_GROUP(node, seed)
+ seed.first << "#{node.object_id} [label=\"()\"];"
super
end
- def visit_CAT(node)
- @nodes << "#{node.object_id} [label=\"○\"];"
+ def visit_CAT(node, seed)
+ seed.first << "#{node.object_id} [label=\"○\"];"
super
end
- def visit_STAR(node)
- @nodes << "#{node.object_id} [label=\"*\"];"
+ def visit_STAR(node, seed)
+ seed.first << "#{node.object_id} [label=\"*\"];"
super
end
- def visit_OR(node)
- @nodes << "#{node.object_id} [label=\"|\"];"
+ def visit_OR(node, seed)
+ seed.first << "#{node.object_id} [label=\"|\"];"
super
end
- def terminal(node)
+ def terminal(node, seed)
value = node.left
- @nodes << "#{node.object_id} [label=\"#{value}\"];"
+ seed.first << "#{node.object_id} [label=\"#{value}\"];"
+ seed
end
+ INSTANCE = new
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 07d97bd6bd..cf4f654ed6 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -8,8 +8,50 @@ require 'active_support/json'
module ActionDispatch
class Request < Rack::Request
def cookie_jar
- env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(env, host, ssl?, cookies)
+ env['action_dispatch.cookies'.freeze] ||= Cookies::CookieJar.build(self, cookies)
end
+
+ # :stopdoc:
+ def have_cookie_jar?
+ env.key? 'action_dispatch.cookies'.freeze
+ end
+
+ def cookie_jar=(jar)
+ env['action_dispatch.cookies'.freeze] = jar
+ end
+
+ def key_generator
+ env[Cookies::GENERATOR_KEY]
+ end
+
+ def signed_cookie_salt
+ env[Cookies::SIGNED_COOKIE_SALT]
+ end
+
+ def encrypted_cookie_salt
+ env[Cookies::ENCRYPTED_COOKIE_SALT]
+ end
+
+ def encrypted_signed_cookie_salt
+ env[Cookies::ENCRYPTED_SIGNED_COOKIE_SALT]
+ end
+
+ def secret_token
+ env[Cookies::SECRET_TOKEN]
+ end
+
+ def secret_key_base
+ env[Cookies::SECRET_KEY_BASE]
+ end
+
+ def cookies_serializer
+ env[Cookies::COOKIES_SERIALIZER]
+ end
+
+ def cookies_digest
+ env[Cookies::COOKIES_DIGEST]
+ end
+ # :startdoc:
end
# \Cookies are read and written through ActionController#cookies.
@@ -118,7 +160,7 @@ module ActionDispatch
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ @permanent ||= PermanentCookieJar.new(self)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -138,10 +180,10 @@ module ActionDispatch
# cookies.signed[:discount] # => 45
def signed
@signed ||=
- if @options[:upgrade_legacy_signed_cookies]
- UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ if upgrade_legacy_signed_cookies?
+ UpgradeLegacySignedCookieJar.new(self)
else
- SignedCookieJar.new(self, @key_generator, @options)
+ SignedCookieJar.new(self)
end
end
@@ -161,10 +203,10 @@ module ActionDispatch
# cookies.encrypted[:discount] # => 45
def encrypted
@encrypted ||=
- if @options[:upgrade_legacy_signed_cookies]
- UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ if upgrade_legacy_signed_cookies?
+ UpgradeLegacyEncryptedCookieJar.new(self)
else
- EncryptedCookieJar.new(self, @key_generator, @options)
+ EncryptedCookieJar.new(self)
end
end
@@ -172,12 +214,26 @@ module ActionDispatch
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
def signed_or_encrypted
@signed_or_encrypted ||=
- if @options[:secret_key_base].present?
+ if request.secret_key_base.present?
encrypted
else
signed
end
end
+
+ protected
+
+ def request; @parent_jar.request; end
+
+ private
+
+ def upgrade_legacy_signed_cookies?
+ request.secret_token.present? && request.secret_key_base.present?
+ end
+
+ def key_generator
+ request.key_generator
+ end
end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
@@ -187,7 +243,7 @@ module ActionDispatch
module VerifyAndUpgradeLegacySignedMessage # :nodoc:
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@@ -216,34 +272,18 @@ module ActionDispatch
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
- def self.options_for_env(env) #:nodoc:
- { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
- encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
- encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
- secret_token: env[SECRET_TOKEN],
- secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
- serializer: env[COOKIES_SERIALIZER],
- digest: env[COOKIES_DIGEST]
- }
- end
-
- def self.build(env, host, secure, cookies)
- key_generator = env[GENERATOR_KEY]
- options = options_for_env env
-
- new(key_generator, host, secure, options).tap do |hash|
+ def self.build(req, cookies)
+ new(req).tap do |hash|
hash.update(cookies)
end
end
- def initialize(key_generator, host = nil, secure = false, options = {})
- @key_generator = key_generator
+ attr_reader :request
+
+ def initialize(request)
@set_cookies = {}
@delete_cookies = {}
- @host = host
- @secure = secure
- @options = options
+ @request = request
@cookies = {}
@committed = false
end
@@ -292,12 +332,12 @@ module ActionDispatch
# if host is not ip and matches domain regexp
# (ip confirms to domain regexp so we explicitly check for ip)
- options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
+ options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}"
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
+ options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') }
end
end
@@ -356,27 +396,20 @@ module ActionDispatch
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end
- def recycle! #:nodoc:
- @set_cookies = {}
- @delete_cookies = {}
- end
-
mattr_accessor :always_write_cookie
self.always_write_cookie = false
private
def write_cookie?(cookie)
- @secure || !cookie[:secure] || always_write_cookie
+ request.ssl? || !cookie[:secure] || always_write_cookie
end
end
class PermanentCookieJar #:nodoc:
include ChainedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
@parent_jar = parent_jar
- @key_generator = key_generator
- @options = options
end
def [](name)
@@ -410,7 +443,7 @@ module ActionDispatch
protected
def needs_migration?(value)
- @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
+ request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end
def serialize(value)
@@ -430,7 +463,7 @@ module ActionDispatch
end
def serializer
- serializer = @options[:serializer] || :marshal
+ serializer = request.cookies_serializer || :marshal
case serializer
when :marshal
Marshal
@@ -442,7 +475,7 @@ module ActionDispatch
end
def digest
- @options[:digest] || 'SHA1'
+ request.cookies_digest || 'SHA1'
end
end
@@ -450,10 +483,9 @@ module ActionDispatch
include ChainedCookieJars
include SerializedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
@parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:signed_cookie_salt])
+ secret = key_generator.generate_key(request.signed_cookie_salt)
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
@@ -505,16 +537,16 @@ module ActionDispatch
include ChainedCookieJars
include SerializedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
+ @parent_jar = parent_jar
+
if ActiveSupport::LegacyKeyGenerator === key_generator
raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
- @parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
- sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
+ secret = key_generator.generate_key(request.encrypted_cookie_salt || '')
+ sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '')
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
@@ -568,9 +600,12 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
+
status, headers, body = @app.call(env)
- if cookie_jar = env['action_dispatch.cookies']
+ if request.have_cookie_jar?
+ cookie_jar = request.cookie_jar
unless cookie_jar.committed?
cookie_jar.write(headers)
if headers[HTTP_HEADER].respond_to?(:join)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 9082aac271..226a688fe2 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -44,6 +44,7 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env)
if headers['X-Cascade'] == 'pass'
@@ -53,14 +54,15 @@ module ActionDispatch
response
rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
+ raise exception unless request.show_exceptions?
render_exception(env, exception)
end
private
def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
+ backtrace_cleaner = env['action_dispatch.backtrace_cleaner']
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
log_error(env, wrapper)
if env['action_dispatch.show_detailed_exceptions']
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 8c3d45584d..039efc3af8 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -31,10 +31,10 @@ module ActionDispatch
'ActionView::Template::Error' => 'template_error'
)
- attr_reader :env, :exception, :line_number, :file
+ attr_reader :backtrace_cleaner, :exception, :line_number, :file
- def initialize(env, exception)
- @env = env
+ def initialize(backtrace_cleaner, exception)
+ @backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
@@ -125,10 +125,6 @@ module ActionDispatch
end
end
- def backtrace_cleaner
- @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
- end
-
def source_fragment(path, line)
return unless Rails.respond_to?(:root) && Rails.root
full_path = Rails.root.join(path)
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 59639a010e..23da169b22 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -8,6 +8,14 @@ module ActionDispatch
def flash
@env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end
+
+ def flash=(flash)
+ @env[Flash::KEY] = flash
+ end
+
+ def flash_hash # :nodoc:
+ @env[Flash::KEY]
+ end
end
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
@@ -263,14 +271,15 @@ module ActionDispatch
end
def call(env)
+ req = ActionDispatch::Request.new env
@app.call(env)
ensure
session = Request::Session.find(env) || {}
- flash_hash = env[KEY]
+ flash_hash = req.flash_hash
if flash_hash && (flash_hash.present? || session.key?('flash'))
session["flash"] = flash_hash.to_session_value
- env[KEY] = flash_hash.dup
+ req.flash = flash_hash.dup
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 2617956c74..402ad778fa 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -1,9 +1,14 @@
-require 'active_support/core_ext/hash/conversions'
require 'action_dispatch/http/request'
-require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
+ # ActionDispatch::ParamsParser works for all the requests having any Content-Length
+ # (like POST). It takes raw data from the request and puts it through the parser
+ # that is picked based on Content-Type header.
+ #
+ # In case of any error while parsing data ParamsParser::ParseError is raised.
class ParamsParser
+ # Raised when raw data from the request cannot be parsed by the parser
+ # defined for request's content mime type.
class ParseError < StandardError
attr_reader :original_exception
@@ -21,35 +26,38 @@ module ActionDispatch
}
}
+ # Create a new +ParamsParser+ middleware instance.
+ #
+ # The +parsers+ argument can take Hash of parsers where key is identifying
+ # content mime type, and value is a lambda that is going to process data.
def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end
def call(env)
- default = env["action_dispatch.request.request_parameters"]
- env["action_dispatch.request.request_parameters"] = parse_formatted_parameters(env, @parsers, default)
+ request = Request.new(env)
+
+ request.request_parameters = parse_formatted_parameters(request, @parsers)
@app.call(env)
end
private
- def parse_formatted_parameters(env, parsers, default)
- request = Request.new(env)
-
- return default if request.content_length.zero?
+ def parse_formatted_parameters(request, parsers)
+ return if request.content_length.zero?
- strategy = parsers.fetch(request.content_mime_type) { return default }
+ strategy = parsers.fetch(request.content_mime_type) { return nil }
strategy.call(request.raw_post)
rescue => e # JSON or Ruby code block errors
- logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
end
- def logger(env)
- env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
+ def logger(request)
+ request.logger || ActiveSupport::Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 9f894e2ec6..aee2334da9 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -74,16 +74,17 @@ module ActionDispatch
# requests. For those requests that do need to know the IP, the
# GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env)
- env["action_dispatch.remote_ip"] = GetIp.new(env, check_ip, proxies)
- @app.call(env)
+ req = ActionDispatch::Request.new env
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
+ @app.call(req.env)
end
# The GetIp class exists as a way to defer processing of the request data
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it.
class GetIp
- def initialize(env, check_ip, proxies)
- @env = env
+ def initialize(req, check_ip, proxies)
+ @req = req
@check_ip = check_ip
@proxies = proxies
end
@@ -108,11 +109,11 @@ module ActionDispatch
# the last address left, which was presumably set by one of those proxies.
def calculate_ip
# Set by the Rack web server, this is a single value.
- remote_addr = ips_from('REMOTE_ADDR').last
+ remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
- client_ips = ips_from('HTTP_CLIENT_IP').reverse
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
+ client_ips = ips_from(@req.client_ip).reverse
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
# If they are both set, it means that this request passed through two
@@ -123,8 +124,8 @@ module ActionDispatch
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " +
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# We assume these things about the IP headers:
@@ -147,8 +148,9 @@ module ActionDispatch
protected
def ips_from(header)
+ return [] unless header
# Split the comma-separated list into an array of strings
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
+ ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
begin
# Only return IPs that are valid according to the IPAddr#new method
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index f0779279c1..12d8dab8eb 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -27,19 +27,21 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
@app.call(env)
rescue Exception => exception
- if env['action_dispatch.show_exceptions'] == false
- raise exception
- else
+ if request.show_exceptions?
render_exception(env, exception)
+ else
+ raise exception
end
end
private
def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
+ backtrace_cleaner = env['action_dispatch.backtrace_cleaner']
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
env["action_dispatch.original_path"] = env["PATH_INFO"]
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index bbf734f103..0430ce3b9a 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -4,36 +4,15 @@ require "active_support/dependencies"
module ActionDispatch
class MiddlewareStack
class Middleware
- attr_reader :args, :block, :name, :classcache
+ attr_reader :args, :block, :klass
- def initialize(klass_or_name, *args, &block)
- @klass = nil
-
- if klass_or_name.respond_to?(:name)
- @klass = klass_or_name
- @name = @klass.name
- else
- @name = klass_or_name.to_s
- end
-
- @classcache = ActiveSupport::Dependencies::Reference
- @args, @block = args, block
+ def initialize(klass, args, block)
+ @klass = klass
+ @args = args
+ @block = block
end
- def klass
- @klass || classcache[@name]
- end
-
- def ==(middleware)
- case middleware
- when Middleware
- klass == middleware.klass
- when Class
- klass == middleware
- else
- normalize(@name) == normalize(middleware)
- end
- end
+ def name; klass.name; end
def inspect
klass.to_s
@@ -42,12 +21,6 @@ module ActionDispatch
def build(app)
klass.new(app, *args, &block)
end
-
- private
-
- def normalize(object)
- object.to_s.strip.sub(/^::/, '')
- end
end
include Enumerable
@@ -75,19 +48,17 @@ module ActionDispatch
middlewares[i]
end
- def unshift(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.unshift(middleware)
+ def unshift(klass, *args, &block)
+ middlewares.unshift(build_middleware(klass, args, block))
end
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
- def insert(index, *args, &block)
+ def insert(index, klass, *args, &block)
index = assert_index(index, :before)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.insert(index, middleware)
+ middlewares.insert(index, build_middleware(klass, args, block))
end
alias_method :insert_before, :insert
@@ -104,26 +75,48 @@ module ActionDispatch
end
def delete(target)
- middlewares.delete target
+ target = get_class target
+ middlewares.delete_if { |m| m.klass == target }
end
- def use(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.push(middleware)
+ def use(klass, *args, &block)
+ middlewares.push(build_middleware(klass, args, block))
end
- def build(app = nil, &block)
- app ||= block
- raise "MiddlewareStack#build requires an app" unless app
+ def build(app = Proc.new)
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
def assert_index(index, where)
- i = index.is_a?(Integer) ? index : middlewares.index(index)
+ index = get_class index
+ i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
i
end
+
+ private
+
+ def get_class(klass)
+ if klass.is_a?(String) || klass.is_a?(Symbol)
+ classcache = ActiveSupport::Dependencies::Reference
+ converted_klass = classcache[klass.to_s]
+ ActiveSupport::Deprecation.warn <<-eowarn
+Passing strings or symbols to the middleware builder is deprecated, please change
+them to actual class references. For example:
+
+ "#{klass}" => #{converted_klass}
+
+ eowarn
+ converted_klass
+ else
+ klass
+ end
+ end
+
+ def build_middleware(klass, args, block)
+ Middleware.new(get_class(klass), args, block)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index f38da4fdf6..9462ae4278 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -48,26 +48,30 @@ module ActionDispatch
end
def call(env)
- path = env['PATH_INFO']
+ serve ActionDispatch::Request.new env
+ end
+
+ def serve(request)
+ path = request.path_info
gzip_path = gzip_file_path(path)
- if gzip_path && gzip_encoding_accepted?(env)
- env['PATH_INFO'] = gzip_path
- status, headers, body = @file_server.call(env)
+ if gzip_path && gzip_encoding_accepted?(request)
+ request.path_info = gzip_path
+ status, headers, body = @file_server.call(request.env)
if status == 304
return [status, headers, body]
end
headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path)
else
- status, headers, body = @file_server.call(env)
+ status, headers, body = @file_server.call(request.env)
end
headers['Vary'] = 'Accept-Encoding' if gzip_path
return [status, headers, body]
ensure
- env['PATH_INFO'] = path
+ request.path_info = path
end
private
@@ -79,8 +83,8 @@ module ActionDispatch
::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end
- def gzip_encoding_accepted?(env)
- env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
+ def gzip_encoding_accepted?(request)
+ request.accept_encoding =~ /\bgzip\b/i
end
def gzip_file_path(path)
@@ -110,16 +114,17 @@ module ActionDispatch
end
def call(env)
- case env['REQUEST_METHOD']
- when 'GET', 'HEAD'
- path = env['PATH_INFO'].chomp('/'.freeze)
+ req = ActionDispatch::Request.new env
+
+ if req.get? || req.head?
+ path = req.path_info.chomp('/'.freeze)
if match = @file_handler.match?(path)
- env['PATH_INFO'] = match
- return @file_handler.call(env)
+ req.path_info = match
+ return @file_handler.serve(req)
end
end
- @app.call(env)
+ @app.call(req.env)
end
end
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index a8a3cd20b9..bae3d909fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -4,8 +4,8 @@ module ActionDispatch
class Request < Rack::Request
# Session is responsible for lazily loading the session from store.
class Session # :nodoc:
- ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
- ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+ ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
+ ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
# Singleton object used to determine if an optional param wasn't specified
Unspecified = Object.new
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 64352d5742..51bafb539f 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/module/remove_method'
require 'active_support/inflector'
+require 'active_support/deprecation'
require 'action_dispatch/routing/redirection'
require 'action_dispatch/routing/endpoint'
@@ -16,7 +17,10 @@ module ActionDispatch
class Constraints < Endpoint #:nodoc:
attr_reader :app, :constraints
- def initialize(app, constraints, dispatcher_p)
+ SERVE = ->(app, req) { app.serve req }
+ CALL = ->(app, req) { app.call req.env }
+
+ def initialize(app, constraints, strategy)
# Unwrap Constraints objects. I don't actually think it's possible
# to pass a Constraints object to this constructor, but there were
# multiple places that kept testing children of this object. I
@@ -26,12 +30,12 @@ module ActionDispatch
app = app.app
end
- @dispatcher = dispatcher_p
+ @strategy = strategy
@app, @constraints, = app, constraints
end
- def dispatcher?; @dispatcher; end
+ def dispatcher?; @strategy == SERVE; end
def matches?(req)
@constraints.all? do |constraint|
@@ -43,11 +47,7 @@ module ActionDispatch
def serve(req)
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
- if dispatcher?
- @app.serve req
- else
- @app.call req.env
- end
+ @strategy.call @app, req
end
private
@@ -59,101 +59,168 @@ module ActionDispatch
class Mapping #:nodoc:
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- attr_reader :requirements, :conditions, :defaults
- attr_reader :to, :default_controller, :default_action, :as, :anchor
+ attr_reader :requirements, :defaults
+ attr_reader :to, :default_controller, :default_action
+ attr_reader :required_defaults, :ast
- def self.build(scope, set, path, as, options)
+ def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
options = scope[:options].merge(options) if scope[:options]
- options.delete :only
- options.delete :except
- options.delete :shallow_path
- options.delete :shallow_prefix
- options.delete :shallow
+ defaults = (scope[:defaults] || {}).dup
+ scope_constraints = scope[:constraints] || {}
- defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
+ new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
+ end
- new scope, set, path, defaults, as, options
+ def self.check_via(via)
+ if via.empty?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise ArgumentError, msg
+ end
+ via
end
- def initialize(scope, set, path, defaults, as, options)
- @requirements, @conditions = {}, {}
- @defaults = defaults
- @set = set
+ def self.normalize_path(path, format)
+ path = Mapper.normalize_path(path)
- @to = options.delete :to
- @default_controller = options.delete(:controller) || scope[:controller]
- @default_action = options.delete(:action) || scope[:action]
- @as = as
- @anchor = options.delete :anchor
+ if format == true
+ "#{path}.:format"
+ elsif optional_format?(path, format)
+ "#{path}(.:format)"
+ else
+ path
+ end
+ end
- formatted = options.delete :format
- via = Array(options.delete(:via) { [] })
- options_constraints = options.delete :constraints
+ def self.optional_format?(path, format)
+ format != false && !path.include?(':format') && !path.end_with?('/')
+ end
- path = normalize_path! path, formatted
- ast = path_ast path
- path_params = path_params ast
+ def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
+ @defaults = defaults
+ @set = set
- options = normalize_options!(options, formatted, path_params, ast, scope[:module])
+ @to = to
+ @default_controller = controller
+ @default_action = default_action
+ @ast = ast
+ @anchor = anchor
+ @via = via
+ path_params = ast.find_all(&:symbol?).map(&:to_sym)
- split_constraints(path_params, scope[:constraints]) if scope[:constraints]
- constraints = constraints(options, path_params)
+ options = add_wildcard_options(options, formatted, ast)
- split_constraints path_params, constraints
+ options = normalize_options!(options, path_params, modyoule)
- @blocks = blocks(options_constraints, scope[:blocks])
+ split_options = constraints(options, path_params)
+
+ constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
if options_constraints.is_a?(Hash)
- split_constraints path_params, options_constraints
- options_constraints.each do |key, default|
- if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
+ @defaults = Hash[options_constraints.find_all { |key, default|
+ URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ }].merge @defaults
+ @blocks = blocks
+ constraints.merge! options_constraints
+ else
+ @blocks = blocks(options_constraints)
end
- normalize_format!(formatted)
+ requirements, conditions = split_constraints path_params, constraints
+ verify_regexp_requirements requirements.map(&:last).grep(Regexp)
- @conditions[:path_info] = path
- @conditions[:parsed_path_info] = ast
+ formats = normalize_format(formatted)
- add_request_method(via, @conditions)
- normalize_defaults!(options)
+ @requirements = formats[:requirements].merge Hash[requirements]
+ @conditions = Hash[conditions]
+ @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
+
+ @required_defaults = (split_options[:required_defaults] || []).map(&:first)
end
- def to_route
- [ app(@blocks), conditions, requirements, defaults, as, anchor ]
+ def make_route(name, precedence)
+ route = Journey::Route.new(name,
+ application,
+ path,
+ conditions,
+ required_defaults,
+ defaults,
+ request_method,
+ precedence)
+
+ route
end
- private
+ def application
+ app(@blocks)
+ end
- def normalize_path!(path, format)
- path = Mapper.normalize_path(path)
+ def path
+ build_path @ast, requirements, @anchor
+ end
- if format == true
- "#{path}.:format"
- elsif optional_format?(path, format)
- "#{path}(.:format)"
- else
- path
- end
- end
+ def conditions
+ build_conditions @conditions, @set.request_class
+ end
+
+ def build_conditions(current_conditions, request_class)
+ conditions = current_conditions.dup
- def optional_format?(path, format)
- format != false && !path.include?(':format') && !path.end_with?('/')
+ conditions.keep_if do |k, _|
+ request_class.public_method_defined?(k)
end
+ end
+ private :build_conditions
+
+ def request_method
+ @via.map { |x| Journey::Route.verb_matcher(x) }
+ end
+ private :request_method
+
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
+
+ def build_path(ast, requirements, anchor)
+ pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
+
+ # Get all the symbol nodes followed by literals that are not the
+ # dummy node.
+ symbols = ast.find_all { |n|
+ n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
+ }.map(&:left)
+
+ # Get all the symbol nodes preceded by literals.
+ symbols.concat ast.find_all { |n|
+ n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
+ }.map { |n| n.right.left }
+
+ symbols.each { |x|
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ }
+
+ pattern
+ end
+ private :build_path
+
- def normalize_options!(options, formatted, path_params, path_ast, modyoule)
+ private
+ def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
if formatted != false
- path_ast.grep(Journey::Nodes::Star) do |node|
- options[node.name.to_sym] ||= /.+?/
- end
+ path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
+ hash[node.name.to_sym] ||= /.+?/
+ }.merge options
+ else
+ options
end
+ end
+ def normalize_options!(options, path_params, modyoule)
if path_params.include?(:controller)
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
@@ -178,74 +245,50 @@ module ActionDispatch
end
def split_constraints(path_params, constraints)
- constraints.each_pair do |key, requirement|
- if path_params.include?(key) || key == :controller
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
- @requirements[key] = requirement
- else
- @conditions[key] = requirement
- end
+ constraints.partition do |key, requirement|
+ path_params.include?(key) || key == :controller
end
end
- def normalize_format!(formatted)
- if formatted == true
- @requirements[:format] ||= /.+/
- elsif Regexp === formatted
- @requirements[:format] = formatted
- @defaults[:format] = nil
- elsif String === formatted
- @requirements[:format] = Regexp.compile(formatted)
- @defaults[:format] = formatted
+ def normalize_format(formatted)
+ case formatted
+ when true
+ { requirements: { format: /.+/ },
+ defaults: {} }
+ when Regexp
+ { requirements: { format: formatted },
+ defaults: { format: nil } }
+ when String
+ { requirements: { format: Regexp.compile(formatted) },
+ defaults: { format: formatted } }
+ else
+ { requirements: { }, defaults: { } }
end
end
- def verify_regexp_requirement(requirement)
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
-
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
- end
- end
+ def verify_regexp_requirements(requirements)
+ requirements.each do |requirement|
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
+ end
- def normalize_defaults!(options)
- options.each_pair do |key, default|
- unless Regexp === default
- @defaults[key] = default
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
end
end
end
- def verify_callable_constraint(callable_constraint)
- unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
- raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
- end
- end
-
- def add_request_method(via, conditions)
- return if via == [:all]
-
- if via.empty?
- msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
- "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
- "If you want to expose your action to GET, use `get` in the router:\n" \
- " Instead of: match \"controller#action\"\n" \
- " Do: get \"controller#action\""
- raise ArgumentError, msg
- end
-
- conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
+ def normalize_defaults(options)
+ Hash[options.reject { |_, default| Regexp === default }]
end
def app(blocks)
if to.respond_to?(:call)
- Constraints.new(to, blocks, false)
+ Constraints.new(to, blocks, Constraints::CALL)
elsif blocks.any?
- Constraints.new(dispatcher(defaults), blocks, true)
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
else
- dispatcher(defaults)
+ dispatcher(defaults.key?(:controller))
end
end
@@ -303,40 +346,29 @@ module ActionDispatch
yield
end
- def blocks(options_constraints, scope_blocks)
- if options_constraints && !options_constraints.is_a?(Hash)
- verify_callable_constraint(options_constraints)
- [options_constraints]
- else
- scope_blocks || []
+ def blocks(callable_constraint)
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
end
+ [callable_constraint]
end
def constraints(options, path_params)
- constraints = {}
- required_defaults = []
- options.each_pair do |key, option|
+ options.group_by do |key, option|
if Regexp === option
- constraints[key] = option
+ :constraints
else
- required_defaults << key unless path_params.include?(key)
+ if path_params.include?(key)
+ :path_params
+ else
+ :required_defaults
+ end
end
end
- @conditions[:required_defaults] = required_defaults
- constraints
- end
-
- def path_params(ast)
- ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
- end
-
- def path_ast(path)
- parser = Journey::Parser.new
- parser.parse path
end
- def dispatcher(defaults)
- @set.dispatcher defaults
+ def dispatcher(raise_on_name_error)
+ @set.dispatcher raise_on_name_error
end
end
@@ -448,10 +480,10 @@ module ActionDispatch
# resources :user, param: :name
#
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
- # model to constructe an URL.
+ # model to construct an URL:
#
# class User < ActiveRecord::Base
- # def to_param # overridden
+ # def to_param
# name
# end
# end
@@ -603,7 +635,7 @@ module ActionDispatch
# Query if the following named route was already defined.
def has_named_route?(name)
- @set.named_routes.routes[name.to_sym]
+ @set.named_routes.key? name
end
private
@@ -685,7 +717,11 @@ module ActionDispatch
def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
- match(*args, options, &block)
+ if options.key?(:defaults)
+ defaults(options.delete(:defaults)) { match(*args, options, &block) }
+ else
+ match(*args, options, &block)
+ end
self
end
end
@@ -788,8 +824,8 @@ module ActionDispatch
end
if options[:constraints].is_a?(Hash)
- defaults = options[:constraints].select do
- |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ defaults = options[:constraints].select do |k, v|
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
end
(options[:defaults] ||= {}).reverse_merge!(defaults)
@@ -797,16 +833,25 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
+ if options.key?(:only) || options.key?(:except)
+ scope[:action_options] = { only: options.delete(:only),
+ except: options.delete(:except) }
+ end
+
+ if options.key? :anchor
+ raise ArgumentError, 'anchor is ignored unless passed to `match`'
+ end
+
@scope.options.each do |option|
if option == :blocks
value = block
elsif option == :options
value = options
else
- value = options.delete(option)
+ value = options.delete(option) { POISON }
end
- if value
+ unless POISON == value
scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
@@ -818,14 +863,18 @@ module ActionDispatch
@scope = @scope.parent
end
+ POISON = Object.new # :nodoc:
+
# Scopes routes to a specific controller
#
# controller "food" do
# match "bacon", action: :bacon, via: :get
# end
- def controller(controller, options={})
- options[:controller] = controller
- scope(options) { yield }
+ def controller(controller)
+ @scope = @scope.new(controller: controller)
+ yield
+ ensure
+ @scope = @scope.parent
end
# Scopes routes to a specific namespace. For example:
@@ -871,13 +920,14 @@ module ActionDispatch
defaults = {
module: path,
- path: options.fetch(:path, path),
as: options.fetch(:as, path),
shallow_path: options.fetch(:path, path),
shallow_prefix: options.fetch(:as, path)
}
- scope(defaults.merge!(options)) { yield }
+ path_scope(options.delete(:path) { path }) do
+ scope(defaults.merge!(options)) { yield }
+ end
end
# === Parameter Restriction
@@ -945,7 +995,10 @@ module ActionDispatch
# end
# Using this, the +:id+ parameter here will default to 'home'.
def defaults(defaults = {})
- scope(:defaults => defaults) { yield }
+ @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
+ yield
+ ensure
+ @scope = @scope.parent
end
private
@@ -977,6 +1030,14 @@ module ActionDispatch
child
end
+ def merge_via_scope(parent, child) #:nodoc:
+ child
+ end
+
+ def merge_format_scope(parent, child) #:nodoc:
+ child
+ end
+
def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
@@ -996,16 +1057,12 @@ module ActionDispatch
end
def merge_options_scope(parent, child) #:nodoc:
- (parent || {}).except(*override_keys(child)).merge!(child)
+ (parent || {}).merge(child)
end
def merge_shallow_scope(parent, child) #:nodoc:
child ? true : false
end
-
- def override_keys(child) #:nodoc:
- child.key?(:only) || child.key?(:except) ? [:only, :except] : []
- end
end
# Resource routing allows you to quickly declare all of the common routes
@@ -1055,17 +1112,19 @@ module ActionDispatch
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- attr_reader :controller, :path, :options, :param
+ attr_reader :controller, :path, :param
- def initialize(entities, api_only = false, options = {})
+ def initialize(entities, api_only, shallow, options = {})
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@as = options[:as]
@param = (options[:param] || :id).to_sym
@options = options
- @shallow = false
+ @shallow = shallow
@api_only = api_only
+ @only = options.delete :only
+ @except = options.delete :except
end
def default_actions
@@ -1077,10 +1136,10 @@ module ActionDispatch
end
def actions
- if only = @options[:only]
- Array(only).map(&:to_sym)
- elsif except = @options[:except]
- default_actions - Array(except).map(&:to_sym)
+ if @only
+ Array(@only).map(&:to_sym)
+ elsif @except
+ default_actions - Array(@except).map(&:to_sym)
else
default_actions
end
@@ -1107,7 +1166,7 @@ module ActionDispatch
end
def resource_scope
- { :controller => controller }
+ controller
end
alias :collection_scope :path
@@ -1130,17 +1189,15 @@ module ActionDispatch
"#{path}/:#{nested_param}"
end
- def shallow=(value)
- @shallow = value
- end
-
def shallow?
@shallow
end
+
+ def singleton?; false; end
end
class SingletonResource < Resource #:nodoc:
- def initialize(entities, api_only, options)
+ def initialize(entities, api_only, shallow, options)
super
@as = nil
@controller = (options[:controller] || plural).to_s
@@ -1168,6 +1225,8 @@ module ActionDispatch
alias :member_scope :path
alias :nested_scope :path
+
+ def singleton?; true; end
end
def resources_path_names(options)
@@ -1202,20 +1261,23 @@ module ActionDispatch
return self
end
- resource_scope(:resource, SingletonResource.new(resources.pop, api_only?, options)) do
- yield if block_given?
+ with_scope_level(:resource) do
+ options = apply_action_options options
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
+ yield if block_given?
- concerns(options[:concerns]) if options[:concerns]
+ concerns(options[:concerns]) if options[:concerns]
- collection do
- post :create
- end if parent_resource.actions.include?(:create)
+ collection do
+ post :create
+ end if parent_resource.actions.include?(:create)
- new do
- get :new
- end if parent_resource.actions.include?(:new)
+ new do
+ get :new
+ end if parent_resource.actions.include?(:new)
- set_member_mappings_for_resource
+ set_member_mappings_for_resource
+ end
end
self
@@ -1360,21 +1422,24 @@ module ActionDispatch
return self
end
- resource_scope(:resources, Resource.new(resources.pop, api_only?, options)) do
- yield if block_given?
+ with_scope_level(:resources) do
+ options = apply_action_options options
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
+ yield if block_given?
- concerns(options[:concerns]) if options[:concerns]
+ concerns(options[:concerns]) if options[:concerns]
- collection do
- get :index if parent_resource.actions.include?(:index)
- post :create if parent_resource.actions.include?(:create)
- end
+ collection do
+ get :index if parent_resource.actions.include?(:index)
+ post :create if parent_resource.actions.include?(:create)
+ end
- new do
- get :new
- end if parent_resource.actions.include?(:new)
+ new do
+ get :new
+ end if parent_resource.actions.include?(:new)
- set_member_mappings_for_resource
+ set_member_mappings_for_resource
+ end
end
self
@@ -1398,7 +1463,7 @@ module ActionDispatch
end
with_scope_level(:collection) do
- scope(parent_resource.collection_scope) do
+ path_scope(parent_resource.collection_scope) do
yield
end
end
@@ -1422,9 +1487,11 @@ module ActionDispatch
with_scope_level(:member) do
if shallow?
- shallow_scope(parent_resource.member_scope) { yield }
+ shallow_scope {
+ path_scope(parent_resource.member_scope) { yield }
+ }
else
- scope(parent_resource.member_scope) { yield }
+ path_scope(parent_resource.member_scope) { yield }
end
end
end
@@ -1435,7 +1502,7 @@ module ActionDispatch
end
with_scope_level(:new) do
- scope(parent_resource.new_scope(action_path(:new))) do
+ path_scope(parent_resource.new_scope(action_path(:new))) do
yield
end
end
@@ -1448,9 +1515,15 @@ module ActionDispatch
with_scope_level(:nested) do
if shallow? && shallow_nesting_depth >= 1
- shallow_scope(parent_resource.nested_scope, nested_options) { yield }
+ shallow_scope do
+ path_scope(parent_resource.nested_scope) do
+ scope(nested_options) { yield }
+ end
+ end
else
- scope(parent_resource.nested_scope, nested_options) { yield }
+ path_scope(parent_resource.nested_scope) do
+ scope(nested_options) { yield }
+ end
end
end
end
@@ -1465,13 +1538,14 @@ module ActionDispatch
end
def shallow
- scope(:shallow => true) do
- yield
- end
+ @scope = @scope.new(shallow: true)
+ yield
+ ensure
+ @scope = @scope.parent
end
def shallow?
- parent_resource.instance_of?(Resource) && @scope[:shallow]
+ !parent_resource.singleton? && @scope[:shallow]
end
# Matches a url pattern to one or more routes.
@@ -1505,8 +1579,6 @@ module ActionDispatch
paths = [path] + rest
end
- options[:anchor] = true unless options.key?(:anchor)
-
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
@@ -1515,48 +1587,85 @@ module ActionDispatch
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
end
- paths.each do |_path|
+ controller = options.delete(:controller) || @scope[:controller]
+ option_path = options.delete :path
+ to = options.delete :to
+ via = Mapping.check_via Array(options.delete(:via) {
+ @scope[:via]
+ })
+ formatted = options.delete(:format) { @scope[:format] }
+ anchor = options.delete(:anchor) { true }
+ options_constraints = options.delete(:constraints) || {}
+
+ path_types = paths.group_by(&:class)
+ path_types.fetch(String, []).each do |_path|
route_options = options.dup
- route_options[:path] ||= _path if _path.is_a?(String)
+ if _path && option_path
+ ActiveSupport::Deprecation.warn <<-eowarn
+Specifying strings for both :path and the route path is deprecated. Change things like this:
+
+ match #{_path.inspect}, :path => #{option_path.inspect}
- path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, route_options)
- route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
- route_options[:to].tr!("-", "_")
+to this:
+
+ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect}
+ eowarn
+ route_options[:action] = _path
+ route_options[:as] = _path
+ _path = option_path
end
+ to = get_to_from_path(_path, to, route_options[:action])
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
+ end
- decomposed_match(_path, route_options)
+ path_types.fetch(Symbol, []).each do |action|
+ route_options = options.dup
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
end
+
self
end
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
+ def get_to_from_path(path, to, action)
+ return to if to || action
+
+ path_without_format = path.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format)
+ path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
+ else
+ nil
+ end
+ end
+
+ def using_match_shorthand?(path)
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
end
- def decomposed_match(path, options) # :nodoc:
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
if on = options.delete(:on)
- send(on) { decomposed_match(path, options) }
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
else
case @scope.scope_level
when :resources
- nested { decomposed_match(path, options) }
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
when :resource
- member { decomposed_match(path, options) }
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
else
- add_route(path, options)
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
end
end
end
- def add_route(action, options) # :nodoc:
- path = path_for_action(action, options.delete(:path))
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
+ path = path_for_action(action, _path)
raise ArgumentError, "path is required" if path.blank?
action = action.to_s
+ default_action = options.delete(:action) || @scope[:action]
+
if action =~ /^[\w\-\/]+$/
- options[:action] ||= action.tr('-', '_') unless action.include?("/")
+ default_action ||= action.tr('-', '_') unless action.include?("/")
else
action = nil
end
@@ -1567,9 +1676,11 @@ module ActionDispatch
name_for_action(options.delete(:as), action)
end
- mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
+ path = Mapping.normalize_path URI.parser.escape(path), formatted
+ ast = Journey::Parser.parse path
+
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
+ @set.add_route(mapping, ast, as, anchor)
end
def root(path, options={})
@@ -1583,7 +1694,7 @@ module ActionDispatch
if @scope.resources?
with_scope_level(:root) do
- scope(parent_resource.path) do
+ path_scope(parent_resource.path) do
super(options)
end
end
@@ -1628,23 +1739,20 @@ module ActionDispatch
return true
end
- unless action_options?(options)
- options.merge!(scope_action_options) if scope_action_options?
- end
-
false
end
- def action_options?(options) #:nodoc:
- options[:only] || options[:except]
+ def apply_action_options(options) # :nodoc:
+ return options if action_options? options
+ options.merge scope_action_options
end
- def scope_action_options? #:nodoc:
- @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
+ def action_options?(options) #:nodoc:
+ options[:only] || options[:except]
end
def scope_action_options #:nodoc:
- @scope[:options].slice(:only, :except)
+ @scope[:action_options] || {}
end
def resource_scope? #:nodoc:
@@ -1659,18 +1767,6 @@ module ActionDispatch
@scope.nested?
end
- def with_exclusive_scope
- begin
- @scope = @scope.new(:as => nil, :path => nil)
-
- with_scope_level(:exclusive) do
- yield
- end
- ensure
- @scope = @scope.parent
- end
- end
-
def with_scope_level(kind)
@scope = @scope.new_level(kind)
yield
@@ -1678,16 +1774,11 @@ module ActionDispatch
@scope = @scope.parent
end
- def resource_scope(kind, resource) #:nodoc:
- resource.shallow = @scope[:shallow]
+ def resource_scope(resource) #:nodoc:
@scope = @scope.new(:scope_level_resource => resource)
- @nesting.push(resource)
- with_scope_level(kind) do
- scope(parent_resource.resource_scope) { yield }
- end
+ controller(resource.resource_scope) { yield }
ensure
- @nesting.pop
@scope = @scope.parent
end
@@ -1700,12 +1791,10 @@ module ActionDispatch
options
end
- def nesting_depth #:nodoc:
- @nesting.size
- end
-
def shallow_nesting_depth #:nodoc:
- @nesting.count(&:shallow?)
+ @scope.find_all { |node|
+ node.frame[:scope_level_resource]
+ }.count { |node| node.frame[:scope_level_resource].shallow? }
end
def param_constraint? #:nodoc:
@@ -1720,27 +1809,28 @@ module ActionDispatch
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scope(path, options = {}) #:nodoc:
+ def shallow_scope #:nodoc:
scope = { :as => @scope[:shallow_prefix],
:path => @scope[:shallow_path] }
@scope = @scope.new scope
- scope(path, options) { yield }
+ yield
ensure
@scope = @scope.parent
end
def path_for_action(action, path) #:nodoc:
- if path.blank? && canonical_action?(action)
+ return "#{@scope[:path]}/#{path}" if path
+
+ if canonical_action?(action)
@scope[:path].to_s
else
- "#{@scope[:path]}/#{action_path(action, path)}"
+ "#{@scope[:path]}/#{action_path(action)}"
end
end
- def action_path(name, path = nil) #:nodoc:
- name = name.to_sym if name.is_a?(String)
- path || @scope[:path_names][name] || name.to_s
+ def action_path(name) #:nodoc:
+ @scope[:path_names][name.to_sym] || name
end
def prefix_name_for_action(as, action) #:nodoc:
@@ -1796,6 +1886,14 @@ module ActionDispatch
def api_only?
@set.api_only?
end
+ private
+
+ def path_scope(path)
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
+ yield
+ ensure
+ @scope = @scope.parent
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
@@ -1906,14 +2004,14 @@ module ActionDispatch
class Scope # :nodoc:
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
:controller, :action, :path_names, :constraints,
- :shallow, :blocks, :defaults, :options]
+ :shallow, :blocks, :defaults, :via, :format, :options]
RESOURCE_SCOPES = [:resource, :resources]
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
attr_reader :parent, :scope_level
- def initialize(hash, parent = {}, scope_level = nil)
+ def initialize(hash, parent = NULL, scope_level = nil)
@hash = hash
@parent = parent
@scope_level = scope_level
@@ -1961,27 +2059,34 @@ module ActionDispatch
end
def new_level(level)
- self.class.new(self, self, level)
- end
-
- def fetch(key, &block)
- @hash.fetch(key, &block)
+ self.class.new(frame, self, level)
end
def [](key)
- @hash.fetch(key) { @parent[key] }
+ scope = find { |node| node.frame.key? key }
+ scope && scope.frame[key]
end
- def []=(k,v)
- @hash[k] = v
+ include Enumerable
+
+ def each
+ node = self
+ loop do
+ break if node.equal? NULL
+ yield node
+ node = node.parent
+ end
end
+
+ def frame; @hash; end
+
+ NULL = Scope.new(nil, nil)
end
def initialize(set) #:nodoc:
@set = set
@scope = Scope.new({ :path_names => @set.resources_path_names })
@concerns = {}
- @nesting = []
end
include Base
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3c1c4fadf6..d6987f4d09 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -24,7 +24,7 @@ module ActionDispatch
def serve(req)
req.check_path_parameters!
uri = URI.parse(path(req.path_parameters, req))
-
+
unless uri.host
if relative_path?(uri.path)
uri.path = "#{req.script_name}/#{uri.path}"
@@ -32,7 +32,7 @@ module ActionDispatch
uri.path = req.script_name.empty? ? "/" : req.script_name
end
end
-
+
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
@@ -124,7 +124,7 @@ module ActionDispatch
url_options[:script_name] = request.script_name
end
end
-
+
ActionDispatch::Http::URL.url_for url_options
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index c968381c26..20926012b4 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -21,8 +21,8 @@ module ActionDispatch
alias inspect to_s
class Dispatcher < Routing::Endpoint
- def initialize(defaults)
- @defaults = defaults
+ def initialize(raise_on_name_error)
+ @raise_on_name_error = raise_on_name_error
@controller_class_names = ThreadSafe::Cache.new
end
@@ -34,12 +34,11 @@ module ActionDispatch
prepare_params!(params)
- # Just raise undefined constant errors if a controller was specified as default.
- unless controller = controller(params, @defaults.key?(:controller))
+ controller = controller(params, @raise_on_name_error) do
return [404, {'X-Cascade' => 'pass'}, []]
end
- dispatch(controller, params[:action], req.env)
+ dispatch(controller, params[:action], req)
end
def prepare_params!(params)
@@ -53,24 +52,26 @@ module ActionDispatch
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
- def controller(params, default_controller=true)
- if params && params.key?(:controller)
- controller_param = params[:controller]
- controller_reference(controller_param)
- end
+ def controller(params, raise_on_name_error=true)
+ controller_reference params.fetch(:controller) { yield }
rescue NameError => e
- raise ActionController::RoutingError, e.message, e.backtrace if default_controller
+ raise ActionController::RoutingError, e.message, e.backtrace if raise_on_name_error
+ yield
end
- private
+ protected
+
+ attr_reader :controller_class_names
def controller_reference(controller_param)
- const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
+ const_name = controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
end
- def dispatch(controller, action, env)
- controller.action(action).call(env)
+ private
+
+ def dispatch(controller, action, req)
+ controller.action(action).call(req.env)
end
def normalize_controller!(params)
@@ -88,6 +89,7 @@ module ActionDispatch
class NamedRouteCollection
include Enumerable
attr_reader :routes, :url_helpers_module, :path_helpers_module
+ private :routes
def initialize
@routes = {}
@@ -142,6 +144,7 @@ module ActionDispatch
end
def key?(name)
+ return unless name
routes.key? name.to_sym
end
@@ -267,7 +270,7 @@ module ActionDispatch
path_params -= controller_options.keys
path_params -= result.keys
end
- inner_options.each do |key, _|
+ inner_options.each_key do |key|
path_params.delete(key)
end
@@ -313,7 +316,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options
+ attr_accessor :default_url_options, :dispatcher_class
attr_reader :env_key
alias :routes :set
@@ -355,7 +358,8 @@ module ActionDispatch
@set = Journey::Routes.new
@router = Journey::Router.new @set
- @formatter = Journey::Formatter.new @set
+ @formatter = Journey::Formatter.new self
+ @dispatcher_class = Routing::RouteSet::Dispatcher
end
def relative_url_root
@@ -413,8 +417,8 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- def dispatcher(defaults)
- Routing::RouteSet::Dispatcher.new(defaults)
+ def dispatcher(raise_on_name_error)
+ dispatcher_class.new(raise_on_name_error)
end
module MountedHelpers
@@ -512,7 +516,7 @@ module ActionDispatch
routes.empty?
end
- def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
+ def add_route(mapping, path_ast, name, anchor)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
if name && named_routes[name]
@@ -523,74 +527,17 @@ module ActionDispatch
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
- path = conditions.delete :path_info
- ast = conditions.delete :parsed_path_info
- required_defaults = conditions.delete :required_defaults
- path = build_path(path, ast, requirements, anchor)
- conditions = build_conditions(conditions)
-
- route = @set.add_route(app, path, conditions, required_defaults, defaults, name)
+ route = @set.add_route(name, mapping)
named_routes[name] = route if name
route
end
- def build_path(path, ast, requirements, anchor)
- strexp = Journey::Router::Strexp.new(
- ast,
- path,
- requirements,
- SEPARATORS,
- anchor)
-
- pattern = Journey::Path::Pattern.new(strexp)
-
- builder = Journey::GTG::Builder.new pattern.spec
-
- # Get all the symbol nodes followed by literals that are not the
- # dummy node.
- symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
- builder.followpos(n).first.literal?
- }
-
- # Get all the symbol nodes preceded by literals.
- symbols.concat pattern.spec.find_all(&:literal?).map { |n|
- builder.followpos(n).first
- }.find_all(&:symbol?)
-
- symbols.each { |x|
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
- }
-
- pattern
- end
- private :build_path
-
- def build_conditions(current_conditions)
- conditions = current_conditions.dup
-
- # Rack-Mount requires that :request_method be a regular expression.
- # :request_method represents the HTTP verb that matches this route.
- #
- # Here we munge values before they get sent on to rack-mount.
- verbs = conditions[:request_method] || []
- unless verbs.empty?
- conditions[:request_method] = %r[^#{verbs.join('|')}$]
- end
-
- conditions.keep_if do |k, _|
- request_class.public_method_defined?(k)
- end
- end
- private :build_conditions
-
class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
- elsif value.is_a?(Array)
- value.map(&:to_param).join('/')
- elsif param = value.to_param
- param
+ else
+ value.to_param
end
end
@@ -815,12 +762,12 @@ module ActionDispatch
if app.matches?(req) && app.dispatcher?
dispatcher = app.app
- if dispatcher.controller(params, false)
- dispatcher.prepare_params!(params)
- return params
- else
+ dispatcher.controller(params, false) do
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
end
+
+ dispatcher.prepare_params!(params)
+ return params
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index d0e3ea818e..54e24ed6bf 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -86,8 +86,8 @@ module ActionDispatch
end
# Load routes.rb if it hasn't been loaded.
- generated_path, extra_keys = @routes.generate_extras(options, defaults)
- found_extras = options.reject { |k, _| ! extra_keys.include? k }
+ generated_path, query_string_keys = @routes.generate_extras(options, defaults)
+ found_extras = options.reject { |k, _| ! query_string_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 0298962409..0cdc6d4e77 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -325,7 +325,11 @@ module ActionDispatch
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
- host! "#{location.host}:#{location.port}" if location.host
+ if url_host = location.host
+ default = Rack::Request::DEFAULT_PORTS[location.scheme]
+ url_host += ":#{location.port}" if default != location.port
+ host! url_host
+ end
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
@@ -374,7 +378,7 @@ module ActionDispatch
@html_document = nil
@url_options = nil
- @controller = session.last_request.env['action_controller.instance']
+ @controller = @request.controller_instance
response.status
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 494644cd46..c28d701b48 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -19,7 +19,7 @@ module ActionDispatch
end
def cookies
- @cookie_jar ||= Cookies::CookieJar.build(@request.env, @request.host, @request.ssl?, @request.cookies)
+ @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies)
end
def redirect_to_url
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index cc610b6d75..60e2cea8a2 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -63,6 +63,10 @@ FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
+SharedTestRoutes.draw do
+ get ':controller(/:action)'
+end
+
module ActionDispatch
module SharedRoutes
def before_setup
@@ -70,35 +74,10 @@ module ActionDispatch
super
end
end
-
- # Hold off drawing routes until all the possible controller classes
- # have been loaded.
- module DrawOnce
- class << self
- attr_accessor :drew
- end
- self.drew = false
-
- def before_setup
- super
- return if DrawOnce.drew
-
- SharedTestRoutes.draw do
- get ':controller(/:action)'
- end
-
- ActionDispatch::IntegrationTest.app.routes.draw do
- get ':controller(/:action)'
- end
-
- DrawOnce.drew = true
- end
- end
end
module ActiveSupport
class TestCase
- include ActionDispatch::DrawOnce
if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
parallelize_me!
end
@@ -119,23 +98,25 @@ class RoutedRackApp
end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
- include ActionDispatch::SharedRoutes
-
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
- middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
- middleware.use "ActionDispatch::DebugExceptions"
- middleware.use "ActionDispatch::Callbacks"
- middleware.use "ActionDispatch::ParamsParser"
- middleware.use "ActionDispatch::Cookies"
- middleware.use "ActionDispatch::Flash"
- middleware.use "Rack::Head"
+ middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use ActionDispatch::DebugExceptions
+ middleware.use ActionDispatch::Callbacks
+ middleware.use ActionDispatch::ParamsParser
+ middleware.use ActionDispatch::Cookies
+ middleware.use ActionDispatch::Flash
+ middleware.use Rack::Head
yield(middleware) if block_given?
end
end
self.app = build_app
+ app.routes.draw do
+ get ':controller(/:action)'
+ end
+
# Stub Rails dispatcher so it does not get controller references and
# simply return the controller#action as Rack::Body.
class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
@@ -149,14 +130,10 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
end
- def self.stub_controllers
- old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
- yield ActionDispatch::Routing::RouteSet.new
- ensure
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
+ def self.stub_controllers(config = nil)
+ route_set = ActionDispatch::Routing::RouteSet.new(*[config].compact)
+ route_set.dispatcher_class = StubDispatcher
+ yield route_set
end
def with_routing(&block)
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 64543f0659..b063d769a4 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -329,7 +329,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
middleware.use ActionDispatch::Flash
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index ed3632007d..0a5e5402b9 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -100,6 +100,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_no_match(/\n/, result)
end
+ test "succesful authentication with uppercase authorization scheme" do
+ @request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, 'Authentication failed when authorization scheme BASIC'
+ end
+
test "authentication request without credential" do
get :display
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 0b9be8d671..dc4c32b07e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -755,6 +755,25 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
end
+ def test_ignores_common_ports_in_host
+ get "http://test.com"
+ assert_equal "test.com", @request.env["HTTP_HOST"]
+
+ get "https://test.com"
+ assert_equal "test.com", @request.env["HTTP_HOST"]
+ end
+
+ def test_keeps_uncommon_ports_in_host
+ get "http://test.com:123"
+ assert_equal "test.com:123", @request.env["HTTP_HOST"]
+
+ get "http://test.com:443"
+ assert_equal "test.com:443", @request.env["HTTP_HOST"]
+
+ get "https://test.com:80"
+ assert_equal "test.com:80", @request.env["HTTP_HOST"]
+ end
+
end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb
deleted file mode 100644
index 537b93387a..0000000000
--- a/actionpack/test/controller/new_base/metal_test.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'abstract_unit'
-
-module MetalTest
- class MetalMiddleware < ActionController::Middleware
- def call(env)
- if env["PATH_INFO"] =~ /authed/
- app.call(env)
- else
- [401, headers, "Not authed!"]
- end
- end
- end
-
- class Endpoint
- def call(env)
- [200, {}, "Hello World"]
- end
- end
-
- class TestMiddleware < ActiveSupport::TestCase
- def setup
- @app = Rack::Builder.new do
- use MetalTest::MetalMiddleware
- run MetalTest::Endpoint.new
- end.to_app
- end
-
- test "it can call the next app by using @app" do
- env = Rack::MockRequest.env_for("/authed")
- response = @app.call(env)
-
- assert_equal ["Hello World"], response[2]
- end
-
- test "it can return a response using the normal AC::Metal techniques" do
- env = Rack::MockRequest.env_for("/")
- response = @app.call(env)
-
- assert_equal ["Not authed!"], response[2]
- assert_equal 401, response[0]
- end
- end
-end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 5a279639cc..dd7c128566 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -505,8 +505,8 @@ class ResourcesTest < ActionController::TestCase
routes = @routes.routes
routes.each do |route|
routes.each do |r|
- next if route === r # skip the comparison instance
- assert_not_equal [route.conditions, route.path.spec.to_s], [r.conditions, r.path.spec.to_s]
+ next if route == r # skip the comparison instance
+ assert_not_equal [route.conditions, route.path.spec.to_s, route.verb], [r.conditions, r.path.spec.to_s, r.verb]
end
end
end
@@ -1128,14 +1128,14 @@ class ResourcesTest < ActionController::TestCase
end
def assert_restful_routes_for(controller_name, options = {})
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || controller_name.to_s
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || controller_name.to_s
if options[:shallow]
options[:shallow_options] ||= {}
- options[:shallow_options][:controller] = options[:options][:controller]
+ options[:shallow_options][:controller] = route_options[:controller]
else
- options[:shallow_options] = options[:options]
+ options[:shallow_options] = route_options
end
new_action = @routes.resources_path_names[:new] || "new"
@@ -1154,7 +1154,7 @@ class ResourcesTest < ActionController::TestCase
edit_member_path = "#{member_path}/#{edit_action}"
formatted_edit_member_path = "#{member_path}/#{edit_action}.xml"
- with_options(options[:options]) do |controller|
+ with_options(route_options) do |controller|
controller.assert_routing collection_path, :action => 'index'
controller.assert_routing new_path, :action => 'new'
controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
@@ -1168,23 +1168,23 @@ class ResourcesTest < ActionController::TestCase
controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
end
- assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
+ assert_recognizes(route_options.merge(:action => 'index'), :path => collection_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create'), :path => collection_path, :method => :post)
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
- assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
+ assert_recognizes(route_options.merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
# test named routes like foo_path and foos_path map to the correct options.
@@ -1195,20 +1195,20 @@ class ResourcesTest < ActionController::TestCase
end
singular_name ||= controller_name.to_s.singularize
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || controller_name.to_s
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || controller_name.to_s
if options[:shallow]
options[:shallow_options] ||= {}
- options[:shallow_options][:controller] = options[:options][:controller]
+ options[:shallow_options][:controller] = route_options[:controller]
else
- options[:shallow_options] = options[:options]
+ options[:shallow_options] = route_options
end
- @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @controller = "#{route_options[:controller].camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- get :index, params: options[:options]
- options[:options].delete :action
+ get :index, params: route_options
+ route_options.delete :action
path = "#{options[:as] || controller_name}"
shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}"
@@ -1223,29 +1223,29 @@ class ResourcesTest < ActionController::TestCase
edit_action = options[:path_names][:edit] || "edit"
end
- assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options]
- assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", route_options
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", route_options.merge(:format => 'xml')
assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
- assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options]
- assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", route_options
+ assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", route_options.merge(:format => 'xml')
assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
def assert_singleton_routes_for(singleton_name, options = {})
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || singleton_name.to_s.pluralize
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
new_path = "#{full_path}/new"
edit_path = "#{full_path}/edit"
formatted_edit_path = "#{full_path}/edit.xml"
- with_options options[:options] do |controller|
+ with_options route_options do |controller|
controller.assert_routing full_path, :action => 'show'
controller.assert_routing new_path, :action => 'new'
controller.assert_routing edit_path, :action => 'edit'
@@ -1254,40 +1254,41 @@ class ResourcesTest < ActionController::TestCase
controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml'
end
- assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
- assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
+ assert_recognizes(route_options.merge(:action => 'show'), :path => full_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'edit'), :path => edit_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create'), :path => full_path, :method => :post)
+ assert_recognizes(route_options.merge(:action => 'update'), :path => full_path, :method => :put)
+ assert_recognizes(route_options.merge(:action => 'destroy'), :path => full_path, :method => :delete)
- assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
- assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
+ assert_recognizes(route_options.merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
+ assert_recognizes(route_options.merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
+ assert_recognizes(route_options.merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
def assert_singleton_named_routes_for(singleton_name, options = {})
- (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
- @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ route_options = (options[:options] ||= {}).dup
+ controller_name = route_options[:controller] || options[:controller] || singleton_name.to_s.pluralize
+ @controller = "#{controller_name.camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- get :show, params: options[:options]
- options[:options].delete :action
+ get :show, params: route_options
+ route_options.delete :action
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
name_prefix = options[:name_prefix]
- assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
- assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
- assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
+ assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
end
def assert_named_route(expected, route, options)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 06ac80356b..feb3e7eab7 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -329,9 +329,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_colon_first
rs.draw do
- get '/:controller/:action/:id', :action => 'index', :id => nil
- get ':url', :controller => 'tiny_url', :action => 'translate'
+ get '/:controller/:action/:id', action: 'index', id: nil
+ get ':url', controller: 'content', action: 'translate'
end
+
+ assert_equal({controller: 'content', action: 'translate', url: 'example'}, rs.recognize_path('/example'))
end
def test_route_with_regexp_for_controller
diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb
index 7a29a7ff97..3dac582407 100644
--- a/actionpack/test/dispatch/exception_wrapper_test.rb
+++ b/actionpack/test/dispatch/exception_wrapper_test.rb
@@ -19,15 +19,13 @@ module ActionDispatch
setup do
Rails.stubs(:root).returns(Pathname.new('.'))
- cleaner = ActiveSupport::BacktraceCleaner.new
- cleaner.add_silencer { |line| line !~ /^lib/ }
-
- @environment = { 'action_dispatch.backtrace_cleaner' => cleaner }
+ @cleaner = ActiveSupport::BacktraceCleaner.new
+ @cleaner.add_silencer { |line| line !~ /^lib/ }
end
test '#source_extracts fetches source fragments for every backtrace entry' do
exception = TestError.new("lib/file.rb:42:in `index'")
- wrapper = ExceptionWrapper.new({}, exception)
+ wrapper = ExceptionWrapper.new(nil, exception)
wrapper.expects(:source_fragment).with('lib/file.rb', 42).returns('foo')
@@ -37,7 +35,7 @@ module ActionDispatch
test '#source_extracts works with Windows paths' do
exc = TestError.new("c:/path/to/rails/app/controller.rb:27:in 'index':")
- wrapper = ExceptionWrapper.new({}, exc)
+ wrapper = ExceptionWrapper.new(nil, exc)
wrapper.expects(:source_fragment).with('c:/path/to/rails/app/controller.rb', 27).returns('nothing')
assert_equal [ code: 'nothing', line_number: 27 ], wrapper.source_extracts
@@ -46,7 +44,7 @@ module ActionDispatch
test '#source_extracts works with non standard backtrace' do
exc = TestError.new('invalid')
- wrapper = ExceptionWrapper.new({}, exc)
+ wrapper = ExceptionWrapper.new(nil, exc)
wrapper.expects(:source_fragment).with('invalid', 0).returns('nothing')
assert_equal [ code: 'nothing', line_number: 0 ], wrapper.source_extracts
@@ -54,14 +52,14 @@ module ActionDispatch
test '#application_trace returns traces only from the application' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal [ "lib/file.rb:42:in `index'" ], wrapper.application_trace
end
test '#application_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.application_trace
assert_equal [], nil_cleaner_wrapper.application_trace
@@ -69,14 +67,14 @@ module ActionDispatch
test '#framework_trace returns traces outside the application' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal caller, wrapper.framework_trace
end
test '#framework_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.framework_trace
assert_equal [], nil_cleaner_wrapper.framework_trace
@@ -84,14 +82,14 @@ module ActionDispatch
test '#full_trace returns application and framework traces' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal exception.backtrace, wrapper.full_trace
end
test '#full_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.full_trace
assert_equal [], nil_cleaner_wrapper.full_trace
@@ -99,7 +97,7 @@ module ActionDispatch
test '#traces returns every trace by category enumerated with an index' do
exception = TestError.new("lib/file.rb:42:in `index'", "/gems/rack.rb:43:in `index'")
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal({
'Application Trace' => [ id: 0, trace: "lib/file.rb:42:in `index'" ],
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index 889f9a4736..f35ffd8845 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -4,13 +4,6 @@ module ActionDispatch
module Routing
class MapperTest < ActiveSupport::TestCase
class FakeSet < ActionDispatch::Routing::RouteSet
- attr_reader :routes
- alias :set :routes
-
- def initialize
- @routes = []
- end
-
def resources_path_names
{}
end
@@ -19,16 +12,24 @@ module ActionDispatch
ActionDispatch::Request
end
- def add_route(*args)
- routes << args
+ def dispatcher_class
+ RouteSet::Dispatcher
+ end
+
+ def defaults
+ routes.map(&:defaults)
end
def conditions
- routes.map { |x| x[1] }
+ routes.map(&:constraints)
end
def requirements
- routes.map { |x| x[2] }
+ routes.map(&:path).map(&:requirements)
+ end
+
+ def asts
+ routes.map(&:path).map(&:spec)
end
end
@@ -36,18 +37,76 @@ module ActionDispatch
Mapper.new FakeSet.new
end
+ def test_scope_raises_on_anchor
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ assert_raises(ArgumentError) do
+ mapper.scope(anchor: false) do
+ end
+ end
+ end
+
+ def test_blows_up_without_via
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ assert_raises(ArgumentError) do
+ mapper.match '/', :to => 'posts#index', :as => :main
+ end
+ end
+
+ def test_unscoped_formatted
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/foo', :to => 'posts#index', :as => :main, :format => true
+ assert_equal({:controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal "/foo.:format", fakeset.asts.first.to_s
+ end
+
+ def test_scoped_formatted
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(format: true) do
+ mapper.get '/foo', :to => 'posts#index', :as => :main
+ end
+ assert_equal({:controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal "/foo.:format", fakeset.asts.first.to_s
+ end
+
+ def test_random_keys
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(omg: :awesome) do
+ mapper.get '/', :to => 'posts#index', :as => :main
+ end
+ assert_equal({:omg=>:awesome, :controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal(/^GET$/, fakeset.routes.first.verb)
+ end
+
def test_mapping_requirements
- options = { :controller => 'foo', :action => 'bar', :via => :get }
- m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', nil, options)
- _, _, requirements, _ = m.to_route
- assert_equal(/.+?/, requirements[:rest])
+ options = { }
+ scope = Mapper::Scope.new({})
+ ast = Journey::Parser.parse '/store/:name(*rest)'
+ m = Mapper::Mapping.build(scope, FakeSet.new, ast, 'foo', 'bar', nil, [:get], nil, {}, true, options)
+ assert_equal(/.+?/, m.requirements[:rest])
+ end
+
+ def test_via_scope
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(via: :put) do
+ mapper.match '/', :to => 'posts#index', :as => :main
+ end
+ assert_equal(/^PUT$/, fakeset.routes.first.verb)
end
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/', :to => 'posts#index', :as => :main
- assert_equal '/', fakeset.conditions.first[:path_info]
+ assert_equal '/', fakeset.asts.first.to_s
end
def test_map_more_slashes
@@ -56,14 +115,14 @@ module ActionDispatch
# FIXME: is this a desired behavior?
mapper.get '/one/two/', :to => 'posts#index', :as => :main
- assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal '/one/two(.:format)', fakeset.asts.first.to_s
end
def test_map_wildcard
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/*path', :to => 'pages#show'
- assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal '/*path(.:format)', fakeset.asts.first.to_s
assert_equal(/.+?/, fakeset.requirements.first[:path])
end
@@ -71,7 +130,7 @@ module ActionDispatch
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/*path/foo/:bar', :to => 'pages#show'
- assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal '/*path/foo/:bar(.:format)', fakeset.asts.first.to_s
assert_equal(/.+?/, fakeset.requirements.first[:path])
end
@@ -79,7 +138,7 @@ module ActionDispatch
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/*foo/*bar', :to => 'pages#show'
- assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal '/*foo/*bar(.:format)', fakeset.asts.first.to_s
assert_equal(/.+?/, fakeset.requirements.first[:foo])
assert_equal(/.+?/, fakeset.requirements.first[:bar])
end
@@ -88,7 +147,7 @@ module ActionDispatch
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/*path', :to => 'pages#show', :format => false
- assert_equal '/*path', fakeset.conditions.first[:path_info]
+ assert_equal '/*path', fakeset.asts.first.to_s
assert_nil fakeset.requirements.first[:path]
end
@@ -96,7 +155,7 @@ module ActionDispatch
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get '/*path', :to => 'pages#show', :format => true
- assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
+ assert_equal '/*path.:format', fakeset.asts.first.to_s
end
def test_raising_helpful_error_on_invalid_arguments
diff --git a/actionpack/test/dispatch/middleware_stack/middleware_test.rb b/actionpack/test/dispatch/middleware_stack/middleware_test.rb
deleted file mode 100644
index 9607f026db..0000000000
--- a/actionpack/test/dispatch/middleware_stack/middleware_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'abstract_unit'
-require 'action_dispatch/middleware/stack'
-
-module ActionDispatch
- class MiddlewareStack
- class MiddlewareTest < ActiveSupport::TestCase
- class Omg; end
-
- {
- 'concrete' => Omg,
- 'anonymous' => Class.new
- }.each do |name, klass|
-
- define_method("test_#{name}_klass") do
- mw = Middleware.new klass
- assert_equal klass, mw.klass
- end
-
- define_method("test_#{name}_==") do
- mw1 = Middleware.new klass
- mw2 = Middleware.new klass
- assert_equal mw1, mw2
- end
-
- end
-
- def test_string_class
- mw = Middleware.new Omg.name
- assert_equal Omg, mw.klass
- end
-
- def test_double_equal_works_with_classes
- k = Class.new
- mw = Middleware.new k
- assert_operator mw, :==, k
-
- result = mw != Class.new
- assert result, 'middleware should not equal other anon class'
- end
-
- def test_double_equal_works_with_strings
- mw = Middleware.new Omg
- assert_operator mw, :==, Omg.name
- end
-
- def test_double_equal_normalizes_strings
- mw = Middleware.new Omg
- assert_operator mw, :==, "::#{Omg.name}"
- end
-
- def test_middleware_loads_classnames_from_cache
- mw = Class.new(Middleware) {
- attr_accessor :classcache
- }.new(Omg.name)
-
- fake_cache = { mw.name => Omg }
- mw.classcache = fake_cache
-
- assert_equal Omg, mw.klass
-
- fake_cache[mw.name] = Middleware
- assert_equal Middleware, mw.klass
- end
-
- def test_middleware_always_returns_class
- mw = Class.new(Middleware) {
- attr_accessor :classcache
- }.new(Omg)
-
- fake_cache = { mw.name => Middleware }
- mw.classcache = fake_cache
-
- assert_equal Omg, mw.klass
- end
- end
- end
-end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 948a690979..33aa616474 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -4,6 +4,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
class FooMiddleware; end
class BarMiddleware; end
class BazMiddleware; end
+ class HiyaMiddleware; end
class BlockMiddleware
attr_reader :block
def initialize(&block)
@@ -17,6 +18,20 @@ class MiddlewareStackTest < ActiveSupport::TestCase
@stack.use BarMiddleware
end
+ def test_delete_with_string_is_deprecated
+ assert_deprecated do
+ assert_difference "@stack.size", -1 do
+ @stack.delete FooMiddleware.name
+ end
+ end
+ end
+
+ def test_delete_works
+ assert_difference "@stack.size", -1 do
+ @stack.delete FooMiddleware
+ end
+ end
+
test "use should push middleware as class onto the stack" do
assert_difference "@stack.size" do
@stack.use BazMiddleware
@@ -25,17 +40,21 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "use should push middleware as a string onto the stack" do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
end
test "use should push middleware as a symbol onto the stack" do
- assert_difference "@stack.size" do
- @stack.use :"MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use :"MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
end
test "use should push middleware class with arguments onto the stack" do
@@ -88,30 +107,28 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "unshift adds a new middleware at the beginning of the stack" do
- @stack.unshift :"MiddlewareStackTest::BazMiddleware"
- assert_equal BazMiddleware, @stack.first.klass
+ assert_deprecated do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
end
test "raise an error on invalid index" do
assert_raise RuntimeError do
- @stack.insert("HiyaMiddleware", BazMiddleware)
+ @stack.insert(HiyaMiddleware, BazMiddleware)
end
assert_raise RuntimeError do
- @stack.insert_after("HiyaMiddleware", BazMiddleware)
+ @stack.insert_after(HiyaMiddleware, BazMiddleware)
end
end
test "lazy evaluates middleware class" do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
- end
-
- test "lazy compares so unloaded constants are not loaded" do
- @stack.use "UnknownMiddleware"
- @stack.use :"MiddlewareStackTest::BazMiddleware"
- assert @stack.include?("::MiddlewareStackTest::BazMiddleware")
end
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 6a439be2b5..d027f09762 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -49,7 +49,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
def test_app_name_is_properly_generated_when_engine_is_mounted_in_resources
assert Router.mounted_helpers.method_defined?(:user_fake_mounted_at_resource),
"A mounted helper should be defined with a parent's prefix"
- assert Router.named_routes.routes[:user_fake_mounted_at_resource],
+ assert Router.named_routes.key?(:user_fake_mounted_at_resource),
"A named route should be defined with a parent's prefix"
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 939a771c65..b36fbd3c76 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -63,6 +63,17 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
assert_equal 'contents', file.read
end
+ test "parses utf8 filename with percent character" do
+ params = parse_multipart('utf8_filename')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_equal 'ファイル%名.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert_equal 'contents', file.read
+ end
+
test "parses boundary problem file" do
params = parse_multipart('boundary_problem_file')
assert_equal %w(file foo), params.keys.sort
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 10fb04e230..8bcc07dd04 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
def test_create_adds_itself_to_env
env = {}
s = Session.create(store, env, {})
- assert_equal s, env[Rack::Session::Abstract::ENV_SESSION_KEY]
+ assert_equal s, env[Rack::RACK_SESSION]
end
def test_to_hash
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index b18a9ab647..332a550de0 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -168,12 +168,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_session_singleton_resource_for_api_app
- self.class.stub_controllers do |_|
- config = ActionDispatch::Routing::RouteSet::Config.new
- config.api_only = true
-
- routes = ActionDispatch::Routing::RouteSet.new(config)
+ config = ActionDispatch::Routing::RouteSet::Config.new
+ config.api_only = true
+ self.class.stub_controllers(config) do |routes|
routes.draw do
resource :session do
get :create
@@ -363,9 +361,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_pagemarks
+ tc = self
draw do
scope "pagemark", :controller => "pagemarks", :as => :pagemark do
- get "new", :path => "build"
+ tc.assert_deprecated do
+ get "new", :path => "build"
+ end
post "create", :as => ""
put "update"
get "remove", :action => :destroy, :as => :remove
@@ -550,11 +551,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_for_api_app
- self.class.stub_controllers do |_|
- config = ActionDispatch::Routing::RouteSet::Config.new
- config.api_only = true
+ config = ActionDispatch::Routing::RouteSet::Config.new
+ config.api_only = true
- routes = ActionDispatch::Routing::RouteSet.new(config)
+ self.class.stub_controllers(config) do |routes|
routes.draw do
resources :projects, controller: :project
end
@@ -3621,7 +3621,7 @@ private
end
class TestAltApp < ActionDispatch::IntegrationTest
- class AltRequest
+ class AltRequest < ActionDispatch::Request
attr_accessor :path_parameters, :path_info, :script_name
attr_reader :env
@@ -3630,6 +3630,7 @@ class TestAltApp < ActionDispatch::IntegrationTest
@env = env
@path_info = "/"
@script_name = ""
+ super
end
def request_method
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index e6a70358c8..dbb996973d 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -170,7 +170,7 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
@cache = ActiveSupport::Cache::MemoryStore.new
middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 715eb90566..e432c65c62 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -348,7 +348,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, options
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 6e9107ecbf..3fed9bad4f 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -192,7 +192,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}"
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
index d30461a623..59c030176b 100644
--- a/actionpack/test/dispatch/session/test_session_test.rb
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -40,4 +40,14 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase
assert_equal %w(one two), session.keys
assert_equal %w(1 2), session.values
end
+
+ def test_fetch_returns_default
+ session = ActionController::TestSession.new(one: '1')
+ assert_equal('2', session.fetch(:two, '2'))
+ end
+
+ def test_fetch_returns_block_value
+ session = ActionController::TestSession.new(one: '1')
+ assert_equal(2, session.fetch('2') { |key| key.to_i })
+ end
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 95971b3a0e..13dec8b618 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -156,7 +156,7 @@ module StaticTests
def test_does_not_modify_path_info
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
- env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip'}
+ env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip', "REQUEST_METHOD" => 'POST'}
@app.call(env)
assert_equal file_name, env['PATH_INFO']
end
diff --git a/actionpack/test/fixtures/multipart/utf8_filename b/actionpack/test/fixtures/multipart/utf8_filename
new file mode 100644
index 0000000000..60738d53b0
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/utf8_filename
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="ファイル%名.txt"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb
index d411a5018a..adf85b860c 100644
--- a/actionpack/test/journey/nodes/symbol_test.rb
+++ b/actionpack/test/journey/nodes/symbol_test.rb
@@ -5,7 +5,7 @@ module ActionDispatch
module Nodes
class TestSymbol < ActiveSupport::TestCase
def test_default_regexp?
- sym = Symbol.new nil
+ sym = Symbol.new "foo"
assert sym.default_regexp?
sym.regexp = nil
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index 6939b426f6..72858f5eda 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -4,6 +4,8 @@ module ActionDispatch
module Journey
module Path
class TestPattern < ActiveSupport::TestCase
+ SEPARATORS = ["/", ".", "?"].join
+
x = /.+/
{
'/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?\Z},
@@ -19,12 +21,12 @@ module ActionDispatch
'/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))\Z},
}.each do |path, expected|
define_method(:"test_to_regexp_#{path}") do
- strexp = Router::Strexp.build(
+ path = Pattern.build(
path,
{ :controller => /.+/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_equal(expected, path.to_regexp)
end
end
@@ -43,13 +45,12 @@ module ActionDispatch
'/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{path}") do
- strexp = Router::Strexp.build(
+ path = Pattern.build(
path,
{ :controller => /.+/ },
- ["/", ".", "?"],
+ SEPARATORS,
false
)
- path = Pattern.new strexp
assert_equal(expected, path.to_regexp)
end
end
@@ -67,27 +68,27 @@ module ActionDispatch
'/:controller/*foo/bar' => %w{ controller foo },
}.each do |path, expected|
define_method(:"test_names_#{path}") do
- strexp = Router::Strexp.build(
+ path = Pattern.build(
path,
{ :controller => /.+/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_equal(expected, path.names)
end
end
def test_to_regexp_with_extended_group
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name',
{ :name => /
#ROFL
(tender|love
#MAO
)/x },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_match(path, '/page/tender')
assert_match(path, '/page/love')
assert_no_match(path, '/page/loving')
@@ -105,23 +106,23 @@ module ActionDispatch
end
def test_to_regexp_match_non_optional
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/:name',
{ :name => /\d+/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_match(path, '/123')
assert_no_match(path, '/')
end
def test_to_regexp_with_group
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name',
{ :name => /(tender|love)/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_match(path, '/page/tender')
assert_match(path, '/page/love')
assert_no_match(path, '/page/loving')
@@ -129,15 +130,13 @@ module ActionDispatch
def test_ast_sets_regular_expressions
requirements = { :name => /(tender|love)/, :value => /./ }
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name/:value',
requirements,
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- assert_equal requirements, strexp.requirements
-
- path = Pattern.new strexp
nodes = path.ast.grep(Nodes::Symbol)
assert_equal 2, nodes.length
nodes.each do |node|
@@ -146,24 +145,24 @@ module ActionDispatch
end
def test_match_data_with_group
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name',
{ :name => /(tender|love)/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
match = path.match '/page/tender'
assert_equal 'tender', match[1]
assert_equal 2, match.length
end
def test_match_data_with_multi_group
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name/:id',
{ :name => /t(((ender|love)))()/ },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
match = path.match '/page/tender/10'
assert_equal 'tender', match[1]
assert_equal '10', match[2]
@@ -173,30 +172,29 @@ module ActionDispatch
def test_star_with_custom_re
z = /\d+/
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/*foo',
{ :foo => z },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_equal(%r{\A/page/(#{z})\Z}, path.to_regexp)
end
def test_insensitive_regexp_with_group
- strexp = Router::Strexp.build(
+ path = Pattern.build(
'/page/:name/aaron',
{ :name => /(tender|love)/i },
- ["/", ".", "?"]
+ SEPARATORS,
+ true
)
- path = Pattern.new strexp
assert_match(path, '/page/TENDER/aaron')
assert_match(path, '/page/loVE/aaron')
assert_no_match(path, '/page/loVE/AAron')
end
def test_to_regexp_with_strexp
- strexp = Router::Strexp.build('/:controller', { }, ["/", ".", "?"])
- path = Pattern.new strexp
+ path = Pattern.build('/:controller', { }, SEPARATORS, true)
x = %r{\A/([^/.?]+)\Z}
assert_equal(x.source, path.source)
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index eff96a0abc..22c3b8113d 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
app = Object.new
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
- route = Route.new("name", app, path, {}, [], defaults)
+ route = Route.build("name", app, path, {}, [], defaults)
assert_equal app, route.app
assert_equal path, route.path
@@ -18,7 +18,7 @@ module ActionDispatch
app = Object.new
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
- route = Route.new("name", app, path, {}, [], defaults)
+ route = Route.build("name", app, path, {}, [], defaults)
route.ast.grep(Nodes::Terminal).each do |node|
assert_equal route, node.memo
@@ -26,30 +26,29 @@ module ActionDispatch
end
def test_path_requirements_override_defaults
- strexp = Router::Strexp.build(':name', { name: /love/ }, ['/'])
- path = Path::Pattern.new strexp
+ path = Path::Pattern.build(':name', { name: /love/ }, '/', true)
defaults = { name: 'tender' }
- route = Route.new('name', nil, path, nil, [], defaults)
+ route = Route.build('name', nil, path, {}, [], defaults)
assert_equal(/love/, route.requirements[:name])
end
def test_ip_address
path = Path::Pattern.from_string '/messages/:id(.:format)'
- route = Route.new("name", nil, path, {:ip => '192.168.1.1'}, [],
+ route = Route.build("name", nil, path, {:ip => '192.168.1.1'}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal '192.168.1.1', route.ip
end
def test_default_ip
path = Path::Pattern.from_string '/messages/:id(.:format)'
- route = Route.new("name", nil, path, {}, [],
+ route = Route.build("name", nil, path, {}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal(//, route.ip)
end
def test_format_with_star
path = Path::Pattern.from_string '/:controller/*extra'
- route = Route.new("name", nil, path, {}, [],
+ route = Route.build("name", nil, path, {}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal '/foo/himom', route.format({
:controller => 'foo',
@@ -59,7 +58,7 @@ module ActionDispatch
def test_connects_all_match
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
- route = Route.new("name", nil, path, {:action => 'bar'}, [], { :controller => 'foo' })
+ route = Route.build("name", nil, path, {:action => 'bar'}, [], { :controller => 'foo' })
assert_equal '/foo/bar/10', route.format({
:controller => 'foo',
@@ -70,21 +69,21 @@ module ActionDispatch
def test_extras_are_not_included_if_optional
path = Path::Pattern.from_string '/page/:id(/:action)'
- route = Route.new("name", nil, path, { }, [], { :action => 'show' })
+ route = Route.build("name", nil, path, { }, [], { :action => 'show' })
assert_equal '/page/10', route.format({ :id => 10 })
end
def test_extras_are_not_included_if_optional_with_parameter
path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
- route = Route.new("name", nil, path, { }, [], { :action => 'show' })
+ route = Route.build("name", nil, path, { }, [], { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10})
end
def test_extras_are_not_included_if_optional_parameter_is_nil
path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
- route = Route.new("name", nil, path, { }, [], { :action => 'show' })
+ route = Route.build("name", nil, path, { }, [], { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10, :section => nil})
end
@@ -94,10 +93,10 @@ module ActionDispatch
defaults = {:controller=>"pages", :action=>"show"}
path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
- specific = Route.new "name", nil, path, constraints, [:controller, :action], defaults
+ specific = Route.build "name", nil, path, constraints, [:controller, :action], defaults
path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
- generic = Route.new "name", nil, path, constraints, [], {}
+ generic = Route.build "name", nil, path, constraints, [], {}
knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 802fb93c69..d512dae4e7 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -4,138 +4,43 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
- attr_reader :routes
+ attr_reader :routes, :mapper
def setup
@app = Routing::RouteSet::Dispatcher.new({})
- @routes = Routes.new
- @router = Router.new(@routes)
- @formatter = Formatter.new(@routes)
- end
-
- class FakeRequestFeeler < Struct.new(:env, :called)
- def new env
- self.env = env
- self
- end
-
- def hello
- self.called = true
- 'world'
- end
-
- def path_info; env['PATH_INFO']; end
- def request_method; env['REQUEST_METHOD']; end
- def ip; env['REMOTE_ADDR']; end
+ @route_set = ActionDispatch::Routing::RouteSet.new
+ @routes = @route_set.router.routes
+ @router = @route_set.router
+ @formatter = @route_set.formatter
+ @mapper = ActionDispatch::Routing::Mapper.new @route_set
end
def test_dashes
- router = Router.new(routes)
-
- exp = Router::Strexp.build '/foo-bar-baz', {}, ['/.?']
- path = Path::Pattern.new exp
-
- routes.add_route nil, path, {}, [], {:id => nil}, {}
+ mapper.get '/foo-bar-baz', to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
- router.recognize(env) do |r, params|
+ @router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
- router = Router.new(routes)
+ mapper.get '/ほげ', to: 'foo#bar'
#match the escaped version of /ほげ
- exp = Router::Strexp.build '/%E3%81%BB%E3%81%92', {}, ['/.?']
- path = Path::Pattern.new exp
-
- routes.add_route nil, path, {}, [], {:id => nil}, {}
-
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
- router.recognize(env) do |r, params|
+ @router.recognize(env) do |r, params|
called = true
end
assert called
end
- def test_request_class_and_requirements_success
- klass = FakeRequestFeeler.new nil
- router = Router.new(routes)
-
- requirements = { :hello => /world/ }
-
- exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
- path = Path::Pattern.new exp
-
- routes.add_route nil, path, requirements, [], {:id => nil}, {}
-
- env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
- router.recognize(env) do |r, params|
- assert_equal({:id => '10'}, params)
- end
-
- assert klass.called, 'hello should have been called'
- assert_equal env.env, klass.env
- end
-
- def test_request_class_and_requirements_fail
- klass = FakeRequestFeeler.new nil
- router = Router.new(routes)
-
- requirements = { :hello => /mom/ }
-
- exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
- path = Path::Pattern.new exp
-
- router.routes.add_route nil, path, requirements, [], {:id => nil}, {}
-
- env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
- router.recognize(env) do |r, params|
- flunk 'route should not be found'
- end
-
- assert klass.called, 'hello should have been called'
- assert_equal env.env, klass.env
- end
-
- class CustomPathRequest < ActionDispatch::Request
- def path_info
- env['custom.path_info']
- end
-
- def path_info=(x)
- env['custom.path_info'] = x
- end
- end
-
- def test_request_class_overrides_path_info
- router = Router.new(routes)
-
- exp = Router::Strexp.build '/bar', {}, ['/.?']
- path = Path::Pattern.new exp
-
- routes.add_route nil, path, {}, [], {}, {}
-
- env = rails_env({'PATH_INFO' => '/foo',
- 'custom.path_info' => '/bar'}, CustomPathRequest)
-
- recognized = false
- router.recognize(env) do |r, params|
- recognized = true
- end
-
- assert recognized, "route should have been recognized"
- end
-
def test_regexp_first_precedence
- add_routes @router, [
- Router::Strexp.build("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
- Router::Strexp.build("/whois/:id(.:format)", {}, ['/', '.', '?'])
- ]
+ mapper.get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
+ mapper.get "/whois/:id(.:format)", to: "foo#baz"
env = rails_env 'PATH_INFO' => '/whois/example.com'
@@ -147,25 +52,21 @@ module ActionDispatch
r = list.first
- assert_equal '/whois/:domain', r.path.spec.to_s
+ assert_equal '/whois/:domain(.:format)', r.path.spec.to_s
end
def test_required_parts_verified_are_anchored
- add_routes @router, [
- Router::Strexp.build("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
- ]
+ mapper.get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(nil, { :id => '10' }, { })
+ @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
end
end
def test_required_parts_are_verified_when_building
- add_routes @router, [
- Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
- ]
+ mapper.get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
- path, _ = @formatter.generate(nil, { :id => '10' }, { })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
assert_raises(ActionController::UrlGenerationError) do
@@ -174,25 +75,22 @@ module ActionDispatch
end
def test_only_required_parts_are_verified
- add_routes @router, [
- Router::Strexp.build("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
- ]
+ mapper.get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
- path, _ = @formatter.generate(nil, { :id => '10' }, { })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
- path, _ = @formatter.generate(nil, { }, { })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { })
assert_equal '/foo', path
- path, _ = @formatter.generate(nil, { :id => 'aa' }, { })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => 'aa' }, { })
assert_equal '/foo/aa', path
end
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
- pattern = Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
- path = Path::Pattern.new pattern
- @router.routes.add_route nil, path, {}, [], {}, route_name
+ mapper = ActionDispatch::Routing::Mapper.new @route_set
+ mapper.get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
@@ -212,7 +110,7 @@ module ActionDispatch
end
def test_X_Cascade
- add_routes @router, [ "/messages(.:format)" ]
+ mapper.get "/messages(.:format)", to: "foo#bar"
resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
assert_equal ['Not Found'], resp.last
assert_equal 'pass', resp[1]['X-Cascade']
@@ -233,24 +131,21 @@ module ActionDispatch
end
def test_defaults_merge_correctly
- path = Path::Pattern.from_string '/foo(/:id)'
- @router.routes.add_route nil, path, {}, [], {:id => nil}, {}
+ mapper.get '/foo(/:id)', to: "foo#bar", id: nil
env = rails_env 'PATH_INFO' => '/foo/10'
@router.recognize(env) do |r, params|
- assert_equal({:id => '10'}, params)
+ assert_equal({:id => '10', :controller => "foo", :action => "bar"}, params)
end
env = rails_env 'PATH_INFO' => '/foo'
@router.recognize(env) do |r, params|
- assert_equal({:id => nil}, params)
+ assert_equal({:id => nil, :controller => "foo", :action => "bar"}, params)
end
end
def test_recognize_with_unbound_regexp
- add_routes @router, [
- Router::Strexp.build("/foo", { }, ['/', '.', '?'], false)
- ]
+ mapper.get "/foo", anchor: false, to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo/bar'
@@ -261,9 +156,7 @@ module ActionDispatch
end
def test_bound_regexp_keeps_path_info
- add_routes @router, [
- Router::Strexp.build("/foo", { }, ['/', '.', '?'], true)
- ]
+ mapper.get "/foo", to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo'
@@ -276,12 +169,14 @@ module ActionDispatch
end
def test_path_not_found
- add_routes @router, [
+ [
"/messages(.:format)",
"/messages/new(.:format)",
"/messages/:id/edit(.:format)",
"/messages/:id(.:format)"
- ]
+ ].each do |path|
+ mapper.get path, to: "foo#bar"
+ end
env = rails_env 'PATH_INFO' => '/messages/unknown/path'
yielded = false
@@ -292,32 +187,29 @@ module ActionDispatch
end
def test_required_part_in_recall
- add_routes @router, [ "/messages/:a/:b" ]
+ mapper.get "/messages/:a/:b", to: "foo#bar"
- path, _ = @formatter.generate(nil, { :a => 'a' }, { :b => 'b' })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :a => 'a' }, { :b => 'b' })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
- add_routes @router, [ "/*path" ]
+ mapper.get "/*path", to: "foo#bar"
- path, _ = @formatter.generate(nil, { }, { :path => 'b' })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { :path => 'b' })
assert_equal "/b", path
end
def test_recall_should_be_used_when_scoring
- add_routes @router, [
- "/messages/:action(/:id(.:format))",
- "/messages/:id(.:format)"
- ]
+ mapper.get "/messages/:action(/:id(.:format))", to: 'foo#bar'
+ mapper.get "/messages/:id(.:format)", to: 'bar#baz'
- path, _ = @formatter.generate(nil, { :id => 10 }, { :action => 'index' })
+ path, _ = @formatter.generate(nil, { :controller => "foo", :id => 10 }, { :action => 'index' })
assert_equal "/messages/index/10", path
end
def test_nil_path_parts_are_ignored
- path = Path::Pattern.from_string "/:controller(/:action(.:format))"
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get "/:controller(/:action(.:format))", to: "tasks#lol"
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
@@ -329,18 +221,14 @@ module ActionDispatch
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
- str = Router::Strexp.build("/", Hash[params], ['/', '.', '?'], true)
- path = Path::Pattern.new str
-
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action)', to: "foo#bar"
parameterized = []
params = [ [:controller, "tasks"],
@@ -356,8 +244,7 @@ module ActionDispatch
end
def test_generate_id
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action)', to: 'foo#bar'
path, params = @formatter.generate(
nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
@@ -366,8 +253,7 @@ module ActionDispatch
end
def test_generate_escapes
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(nil,
{ :controller => "tasks",
@@ -377,8 +263,7 @@ module ActionDispatch
end
def test_generate_escapes_with_namespaced_controller
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(
nil, { :controller => "admin/tasks",
@@ -388,8 +273,7 @@ module ActionDispatch
end
def test_generate_extra_params
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action)', to: "foo#bar"
path, params = @formatter.generate(
nil, { :id => 1,
@@ -402,8 +286,7 @@ module ActionDispatch
end
def test_generate_missing_keys_no_matches_different_format_keys
- path = Path::Pattern.from_string '/:controller/:action/:name'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller/:action/:name', to: "foo#bar"
primarty_parameters = {
:id => 1,
:controller => "tasks",
@@ -429,8 +312,7 @@ module ActionDispatch
end
def test_generate_uses_recall_if_needed
- path = Path::Pattern.from_string '/:controller(/:action(/:id))'
- @router.routes.add_route @app, path, {}, [], {}, {}
+ mapper.get '/:controller(/:action(/:id))', to: "foo#bar"
path, params = @formatter.generate(
nil,
@@ -441,8 +323,7 @@ module ActionDispatch
end
def test_generate_with_name
- path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, [], {}, "tasks"
+ mapper.get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
path, params = @formatter.generate(
"tasks",
@@ -458,16 +339,15 @@ module ActionDispatch
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
- path = Path::Pattern.from_string "/:controller(/:action(/:id))"
- app = Object.new
- route = @router.routes.add_route(app, path, {}, [], {}, {})
+ mapper.get "/:controller(/:action(/:id))", to: 'foo#bar'
+ route = @routes.first
env = rails_env 'PATH_INFO' => request_path
called = false
@router.recognize(env) do |r, params|
assert_equal route, r
- assert_equal(expected, params)
+ assert_equal({ :action => "bar" }.merge(expected), params)
called = true
end
@@ -480,16 +360,15 @@ module ActionDispatch
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
- path = Path::Pattern.from_string '/:segment/*splat'
- app = Object.new
- route = @router.routes.add_route(app, path, {}, [], {}, {})
+ mapper.get '/:segment/*splat', to: 'foo#bar'
env = rails_env 'PATH_INFO' => request_path
called = false
+ route = @routes.first
@router.recognize(env) do |r, params|
assert_equal route, r
- assert_equal(expected, params)
+ assert_equal(expected.merge(:controller=>"foo", :action=>"bar"), params)
called = true
end
@@ -498,14 +377,8 @@ module ActionDispatch
end
def test_namespaced_controller
- strexp = Router::Strexp.build(
- "/:controller(/:action(/:id))",
- { :controller => /.+?/ },
- ["/", ".", "?"]
- )
- path = Path::Pattern.new strexp
- app = Object.new
- route = @router.routes.add_route(app, path, {}, [], {}, {})
+ mapper.get "/:controller(/:action(/:id))", { :controller => /.+?/ }
+ route = @routes.first
env = rails_env 'PATH_INFO' => '/admin/users/show/10'
called = false
@@ -524,9 +397,8 @@ module ActionDispatch
end
def test_recognize_literal
- path = Path::Pattern.from_string "/books(/:action(.:format))"
- app = Object.new
- route = @router.routes.add_route(app, path, {}, [], {:controller => 'books'})
+ mapper.get "/books(/:action(.:format))", controller: "books"
+ route = @routes.first
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
@@ -541,10 +413,7 @@ module ActionDispatch
end
def test_recognize_head_route
- path = Path::Pattern.from_string "/books(/:action(.:format))"
- app = Object.new
- conditions = { request_method: 'HEAD' }
- @router.routes.add_route(app, path, conditions, [], {})
+ mapper.match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
env = rails_env(
'PATH_INFO' => '/books/list.rss',
@@ -560,12 +429,7 @@ module ActionDispatch
end
def test_recognize_head_request_as_get_route
- path = Path::Pattern.from_string "/books(/:action(.:format))"
- app = Object.new
- conditions = {
- :request_method => 'GET'
- }
- @router.routes.add_route(app, path, conditions, [], {})
+ mapper.get "/books(/:action(.:format))", to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "HEAD"
@@ -578,11 +442,8 @@ module ActionDispatch
assert called
end
- def test_recognize_cares_about_verbs
- path = Path::Pattern.from_string "/books(/:action(.:format))"
- app = Object.new
- conditions = { request_method: 'GET' }
- @router.routes.add_route(app, path, conditions, [], {})
+ def test_recognize_cares_about_get_verbs
+ mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :get
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
@@ -593,31 +454,58 @@ module ActionDispatch
end
assert_not called
+ end
- conditions = conditions.dup
- conditions[:request_method] = 'POST'
+ def test_recognize_cares_about_post_verbs
+ mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :post
- post = @router.routes.add_route(app, path, conditions, [], {})
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => "POST"
called = false
@router.recognize(env) do |r, params|
- assert_equal post, r
called = true
end
assert called
end
+ def test_multi_verb_recognition
+ mapper.match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
+
+ %w( POST GET ).each do |verb|
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => verb
+
+ called = false
+ @router.recognize(env) do |r, params|
+ called = true
+ end
+
+ assert called
+ end
+
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => 'PUT'
+
+ called = false
+ @router.recognize(env) do |r, params|
+ called = true
+ end
+
+ assert_not called
+ end
+
private
- def add_routes router, paths
+ def add_routes router, paths, anchor = true
paths.each do |path|
if String === path
path = Path::Pattern.from_string path
else
- path = Path::Pattern.new path
+ path
end
- router.routes.add_route @app, path, {}, [], {}, {}
+ add_route @app, path, {}, [], {}, {}
end
end
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index b9dac8751c..f8293dfc5f 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -3,18 +3,19 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRoutes < ActiveSupport::TestCase
- setup do
- @routes = Routes.new
+ attr_reader :routes, :mapper
+
+ def setup
+ @route_set = ActionDispatch::Routing::RouteSet.new
+ @routes = @route_set.router.routes
+ @router = @route_set.router
+ @mapper = ActionDispatch::Routing::Mapper.new @route_set
+ super
end
def test_clear
- routes = Routes.new
- exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
- path = Path::Pattern.new exp
- requirements = { :hello => /world/ }
-
- routes.add_route nil, path, requirements, [], {:id => nil}, {}
- assert_not routes.empty?
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron'
+ assert_not_predicate routes, :empty?
assert_equal 1, routes.length
routes.clear
@@ -23,52 +24,36 @@ module ActionDispatch
end
def test_ast
- routes = Routes.new
- path = Path::Pattern.from_string '/hello'
-
- routes.add_route nil, path, {}, [], {}, {}
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron'
ast = routes.ast
- routes.add_route nil, path, {}, [], {}, {}
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'gorby'
assert_not_equal ast, routes.ast
end
def test_simulator_changes
- routes = Routes.new
- path = Path::Pattern.from_string '/hello'
-
- routes.add_route nil, path, {}, [], {}, {}
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron'
sim = routes.simulator
- routes.add_route nil, path, {}, [], {}, {}
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'gorby'
assert_not_equal sim, routes.simulator
end
def test_partition_route
- path = Path::Pattern.from_string '/hello'
+ mapper.get "/foo(/:id)", to: "foo#bar", as: 'aaron'
- anchored_route = @routes.add_route nil, path, {}, [], {}, {}
- assert_equal [anchored_route], @routes.anchored_routes
- assert_equal [], @routes.custom_routes
+ assert_equal 1, @routes.anchored_routes.length
+ assert_predicate @routes.custom_routes, :empty?
- strexp = Router::Strexp.build(
- "/hello/:who", { who: /\d/ }, ['/', '.', '?']
- )
- path = Path::Pattern.new strexp
+ mapper.get "/hello/:who", to: "foo#bar", as: 'bar', who: /\d/
- custom_route = @routes.add_route nil, path, {}, [], {}, {}
- assert_equal [custom_route], @routes.custom_routes
- assert_equal [anchored_route], @routes.anchored_routes
+ assert_equal 1, @routes.custom_routes.length
+ assert_equal 1, @routes.anchored_routes.length
end
def test_first_name_wins
- routes = Routes.new
-
- one = Path::Pattern.from_string '/hello'
- two = Path::Pattern.from_string '/aaron'
-
- routes.add_route nil, one, {}, [], {}, 'aaron'
- routes.add_route nil, two, {}, [], {}, 'aaron'
-
- assert_equal '/hello', routes.named_routes['aaron'].path.spec.to_s
+ mapper.get "/hello", to: "foo#bar", as: 'aaron'
+ assert_raise(ArgumentError) do
+ mapper.get "/aaron", to: "foo#bar", as: 'aaron'
+ end
end
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 55a2e155d1..90c9c2171c 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,12 @@
+* Make `disable_with` the default behavior for submit tags. Disables the
+ button on submit to prevent double submits.
+
+ *Justin Schiff*
+
+* Add a break_sequence option to word_wrap so you can specify a custom break.
+
+ * Mauricio Gomez *
+
* Add wildcard matching to explicit dependencies.
Turns:
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 43124bb904..72ca6f7ec6 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -161,6 +161,10 @@ module ActionView #:nodoc:
cattr_accessor :raise_on_missing_translations
@@raise_on_missing_translations = false
+ # Specify whether submit_tag should automatically disable on click
+ cattr_accessor :automatically_disable_submit_tag
+ @@automatically_disable_submit_tag = true
+
class_attribute :_routes
class_attribute :logger
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 3beb7fefb1..717b326740 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -31,26 +31,33 @@ module ActionView
# stylesheet_link_tag("application")
# # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
- # Browsers typically open at most two simultaneous connections to a single
- # host, which means your assets often have to wait for other assets to finish
- # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
- # +asset_host+. For example, "assets%d.example.com". If that wildcard is
- # present Rails distributes asset requests among the corresponding four hosts
- # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
- # will open eight simultaneous connections rather than two.
+ # Browsers open a limited number of simultaneous connections to a single
+ # host. The exact number varies by browser and version. This limit may cause
+ # some asset downloads to wait for previous assets to finish before they can
+ # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
+ # distribute the requests over four hosts. For example,
+ # <tt>assets%d.example.com<tt> will spread the asset requests over
+ # "assets0.example.com", ..., "assets3.example.com".
#
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
- # To do this, you can either setup four actual hosts, or you can use wildcard
- # DNS to CNAME the wildcard to a single asset host. You can read more about
- # setting up your DNS CNAME records from your ISP.
+ # This may improve the asset loading performance of your application.
+ # It is also possible the combination of additional connection overhead
+ # (DNS, SSL) and the overall browser connection limits may result in this
+ # solution being slower. You should be sure to measure your actual
+ # performance across targeted browsers both before and after this change.
+ #
+ # To implement the corresponding hosts you can either setup four actual
+ # hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
+ # You can read more about setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background.
+ # for background and http://www.browserscope.org/?category=network for
+ # connection limit data.
#
# Alternatively, you can exert more control over the asset host by setting
# +asset_host+ to a proc like this:
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 94fbaa2dca..312e41ee48 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -228,6 +228,7 @@ module ActionView
# or the given prompt string.
# * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
# automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
+ # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index d36701955a..af684e05c6 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -414,34 +414,48 @@ module ActionView
# the form is processed normally, otherwise no action is taken.
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
# disabled version of the submit button when the form is submitted. This feature is
- # provided by the unobtrusive JavaScript driver.
+ # provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag
+ # pass <tt>:data => { disable_with: false }</tt> Defaults to value attribute.
#
# ==== Examples
# submit_tag
- # # => <input name="commit" type="submit" value="Save changes" />
+ # # => <input name="commit" data-disable-with="Save changes" type="submit" value="Save changes" />
#
# submit_tag "Edit this article"
- # # => <input name="commit" type="submit" value="Edit this article" />
+ # # => <input name="commit" data-disable-with="Edit this article" type="submit" value="Edit this article" />
#
# submit_tag "Save edits", disabled: true
- # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
+ # # => <input disabled="disabled" name="commit" data-disable-with="Save edits" type="submit" value="Save edits" />
#
- # submit_tag "Complete sale", data: { disable_with: "Please wait..." }
- # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
+ # submit_tag "Complete sale", data: { disable_with: "Submitting..." }
+ # # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" />
#
# submit_tag nil, class: "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
# submit_tag "Edit", class: "edit_button"
- # # => <input class="edit_button" name="commit" type="submit" value="Edit" />
+ # # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
#
# submit_tag "Save", data: { confirm: "Are you sure?" }
- # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
+ # # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys
+ tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)
+
+ if ActionView::Base.automatically_disable_submit_tag
+ unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false)
+ disable_with_text = tag_options["data-disable-with"]
+ disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"]
+ disable_with_text ||= value.clone
+ tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
+ else
+ tag_options.delete("data-disable-with")
+ tag_options["data"].delete(:disable_with) if tag_options["data"]
+ end
+ end
- tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
+ tag :input, tag_options
end
# Creates a button element that defines a <tt>submit</tt> button,
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index a2e9f37453..191a881de0 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -120,7 +120,7 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # Provided strictly for compabitility and can be removed in Rails 5.
+ # Provided strictly for compatibility and can be removed in Rails 5.
def sanitizer_vendor
Rails::Html::Sanitizer
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 6a3d01667d..432693bc23 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -250,12 +250,15 @@ module ActionView
#
# word_wrap('Once upon a time', line_width: 1)
# # => Once\nupon\na\ntime
- def word_wrap(text, options = {})
- line_width = options.fetch(:line_width, 80)
-
+ #
+ # You can also specify a custom +break_sequence+ ("\n" by default)
+ #
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
+ # # => Once\r\nupon\r\na\r\ntime
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
text.split("\n").collect! do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
- end * "\n"
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
+ end * break_sequence
end
# Returns +text+ transformed into HTML using simple formatting rules.
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index c4bc26ca8a..f6b5696a13 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -263,9 +263,15 @@ module ActionView
end
def method_missing(selector, *args)
- if @controller.respond_to?(:_routes) &&
- ( @controller._routes.named_routes.route_defined?(selector) ||
- @controller._routes.mounted_helpers.method_defined?(selector) )
+ begin
+ routes = @controller.respond_to?(:_routes) && @controller._routes
+ rescue
+ # Dont call routes, if there is an error on _routes call
+ end
+
+ if routes &&
+ ( routes.named_routes.route_defined?(selector) ||
+ routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
super
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 4635c645d0..35f520ed1c 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -147,13 +147,13 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
- middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
- middleware.use "ActionDispatch::DebugExceptions"
- middleware.use "ActionDispatch::Callbacks"
- middleware.use "ActionDispatch::ParamsParser"
- middleware.use "ActionDispatch::Cookies"
- middleware.use "ActionDispatch::Flash"
- middleware.use "Rack::Head"
+ middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use ActionDispatch::DebugExceptions
+ middleware.use ActionDispatch::Callbacks
+ middleware.use ActionDispatch::ParamsParser
+ middleware.use ActionDispatch::Cookies
+ middleware.use ActionDispatch::Flash
+ middleware.use Rack::Head
yield(middleware) if block_given?
end
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index b8cb5bd746..37f2d7cad6 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1577,7 +1577,7 @@ class FormHelperTest < ActionView::TestCase
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
- "<input name='commit' type='submit' value='Create post' />" +
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" +
"<button name='button' type='submit'>Create post</button>" +
"<button name='button' type='submit'><span>Create post</span></button>"
end
@@ -1854,7 +1854,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do
"<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" +
- "<input name='commit' type='submit' value='Edit post' />"
+ "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"
end
assert_dom_equal expected, output_buffer
@@ -1875,7 +1875,7 @@ class FormHelperTest < ActionView::TestCase
"<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
- "<input name='commit' value='Create post' type='submit' />"
+ "<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"
end
assert_dom_equal expected, output_buffer
@@ -2083,7 +2083,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
"<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
"<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" +
- "<input name='commit' type='submit' value='Create post' />"
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
assert_dom_equal expected, output_buffer
@@ -2101,7 +2101,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
"<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
"<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" +
- "<input name='commit' type='submit' value='Create post' />"
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
assert_dom_equal expected, output_buffer
@@ -2226,7 +2226,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts', 'new_post', 'new_post') do
- "<input name='commit' type='submit' value='Create Post' />"
+ "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />"
end
assert_dom_equal expected, output_buffer
@@ -2240,7 +2240,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
- "<input name='commit' type='submit' value='Confirm Post changes' />"
+ "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />"
end
assert_dom_equal expected, output_buffer
@@ -2254,7 +2254,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form do
- "<input name='commit' class='extra' type='submit' value='Save changes' />"
+ "<input name='commit' class='extra' data-disable-with='Save changes' type='submit' value='Save changes' />"
end
assert_dom_equal expected, output_buffer
@@ -2268,7 +2268,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', method: 'patch') do
- "<input name='commit' type='submit' value='Update your Post' />"
+ "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
end
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index f602c82c42..a9d9562580 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -433,6 +433,44 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_empty_submit_tag
+ assert_dom_equal(
+ %(<input data-disable-with="Save" name='commit' type="submit" value="Save" />),
+ submit_tag("Save")
+ )
+ end
+
+ def test_empty_submit_tag_with_opt_out
+ ActionView::Base.automatically_disable_submit_tag = false
+ assert_dom_equal(
+ %(<input name='commit' type="submit" value="Save" />),
+ submit_tag("Save")
+ )
+ ensure
+ ActionView::Base.automatically_disable_submit_tag = true
+ end
+
+ def test_data_disable_with_string
+ assert_dom_equal(
+ %(<input data-disable-with="Processing..." data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
+ submit_tag("Save", { "data-disable-with" => "Processing...", "data-confirm" => "Are you sure?" })
+ )
+ end
+
+ def test_data_disable_with_boolean
+ assert_dom_equal(
+ %(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
+ submit_tag("Save", { "data-disable-with" => false, "data-confirm" => "Are you sure?" })
+ )
+ end
+
+ def test_data_hash_disable_with_boolean
+ assert_dom_equal(
+ %(<input data-confirm="Are you sure?" name='commit' type="submit" value="Save" />),
+ submit_tag("Save", { :data => { :confirm => "Are you sure?", :disable_with => false } })
+ )
+ end
+
def test_submit_tag_with_no_onclick_options
assert_dom_equal(
%(<input name='commit' data-disable-with="Saving..." type="submit" value="Save" />),
@@ -442,7 +480,7 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag_with_confirmation
assert_dom_equal(
- %(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />),
+ %(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?" data-disable-with="Save" />),
submit_tag("Save", :data => { :confirm => "Are you sure?" })
)
end
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 05c3dc0613..80fbaa8392 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -209,7 +209,7 @@ module ActionView
end
test "is able to use routes" do
- controller.request.assign_parameters(@routes, 'foo', 'index')
+ controller.request.assign_parameters(@routes, 'foo', 'index', {}, '/foo', [])
assert_equal '/foo', url_for
assert_equal '/bar', url_for(:controller => 'bar')
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 5791f33069..fae1965ffa 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -366,6 +366,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
+ def test_word_wrap_with_custom_break_sequence
+ assert_equal("1234567890\r\n1234567890\r\n1234567890", word_wrap("1234567890 " * 3, line_width: 2, break_sequence: "\r\n"))
+ end
+
def test_pluralization
assert_equal("1 count", pluralize(1, "count"))
assert_equal("2 counts", pluralize(2, "count"))
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index c523723fc4..637096e935 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,20 @@
+* Implement `provider_job_id` for `queue_classic` adapter. This requires the
+ latest, currently unreleased, version of queue_classic.
+
+ *Yves Senn*
+
+* `assert_enqueued_with` and `assert_performed_with` now returns the matched
+ job instance for further assertions.
+
+ *Jean Boussier*
+
+* Include I18n.locale into job serialization/deserialization and use it around
+ `perform`.
+
+ Fixes #20799.
+
+ *Johannes Opper*
+
* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
`ActiveJob::Base` as `provider_job_id`.
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index fd49b3fda5..5d7c4cfb91 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -5,6 +5,7 @@ require 'active_job/enqueuing'
require 'active_job/execution'
require 'active_job/callbacks'
require 'active_job/logging'
+require 'active_job/translation'
module ActiveJob #:nodoc:
# = Active Job
@@ -60,6 +61,7 @@ module ActiveJob #:nodoc:
include Execution
include Callbacks
include Logging
+ include Translation
ActiveSupport.run_load_hooks(:active_job, self)
end
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 0528572cd0..eac7279309 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -20,6 +20,9 @@ module ActiveJob
# ID optionally provided by adapter
attr_accessor :provider_job_id
+
+ # I18n.locale to be used during the job.
+ attr_accessor :locale
end
# These methods will be included into any Active Job object, adding
@@ -68,7 +71,8 @@ module ActiveJob
'job_class' => self.class.name,
'job_id' => job_id,
'queue_name' => queue_name,
- 'arguments' => serialize_arguments(arguments)
+ 'arguments' => serialize_arguments(arguments),
+ 'locale' => I18n.locale
}
end
@@ -96,6 +100,7 @@ module ActiveJob
self.job_id = job_data['job_id']
self.queue_name = job_data['queue_name']
self.serialized_arguments = job_data['arguments']
+ self.locale = job_data['locale'] || I18n.locale
end
private
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 1335e3236e..e8ceabaeba 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -15,18 +15,18 @@ module ActiveJob
#
# === Backends Features
#
- # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
- # |-------------------|-------|--------|-----------|------------|---------|---------|
- # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
- # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
- # | Qu | Yes | Yes | No | No | No | 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 |
+ # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
+ # |-------------------|-------|--------|------------|------------|---------|---------|
+ # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
+ # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
+ # | Qu | Yes | Yes | No | No | No | Global |
+ # | Que | Yes | Yes | Yes | Job | No | Job |
+ # | queue_classic | Yes | Yes | Yes* | 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 |
#
# ==== Async
#
@@ -50,9 +50,8 @@ module ActiveJob
# N/A: The adapter does not support queueing.
#
# NOTE:
- # queue_classic does not support job scheduling.
- # However, you can use the queue_classic-later gem.
- # See the documentation for ActiveJob::QueueAdapters::QueueClassicAdapter.
+ # queue_classic supports job scheduling since version 3.1.
+ # For older versions you can use the queue_classic-later gem.
#
# ==== Priorities
#
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 059754a87f..0ee41407d8 100644
--- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -18,7 +18,9 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :queue_classic
class QueueClassicAdapter
def enqueue(job) #:nodoc:
- build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
+ qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
+ qc_job
end
def enqueue_at(job, timestamp) #:nodoc:
@@ -28,7 +30,9 @@ module ActiveJob
'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.serialize)
+ qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
+ qc_job
end
# Builds a <tt>QC::Queue</tt> object to schedule jobs on.
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index e71a3b4f85..200d82838e 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -233,10 +233,11 @@ module ActiveJob
args.assert_valid_keys(:job, :args, :at, :queue)
serialized_args = serialize_args_for_assertion(args)
yield
- matching_job = enqueued_jobs.any? do |job|
+ matching_job = enqueued_jobs.find do |job|
serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No enqueued job found with #{args}"
+ instantiate_job(matching_job)
ensure
queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs
end
@@ -254,10 +255,11 @@ module ActiveJob
args.assert_valid_keys(:job, :args, :at, :queue)
serialized_args = serialize_args_for_assertion(args)
perform_enqueued_jobs { yield }
- matching_job = performed_jobs.any? do |job|
+ matching_job = performed_jobs.find do |job|
serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No performed job found with #{args}"
+ instantiate_job(matching_job)
ensure
queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
end
@@ -311,6 +313,13 @@ module ActiveJob
end
serialized_args
end
+
+ def instantiate_job(payload)
+ job = payload[:job].new(*payload[:args])
+ job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
+ job.queue_name = payload[:queue]
+ job
+ end
end
end
end
diff --git a/activejob/lib/active_job/translation.rb b/activejob/lib/active_job/translation.rb
new file mode 100644
index 0000000000..67e4cf4ab9
--- /dev/null
+++ b/activejob/lib/active_job/translation.rb
@@ -0,0 +1,11 @@
+module ActiveJob
+ module Translation #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ around_perform do |job, block, _|
+ I18n.with_locale(job.locale, &block)
+ end
+ end
+ end
+end
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index db22783030..229517774e 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -1,5 +1,6 @@
require 'helper'
require 'jobs/gid_job'
+require 'jobs/hello_job'
require 'models/person'
class JobSerializationTest < ActiveSupport::TestCase
@@ -12,4 +13,20 @@ class JobSerializationTest < ActiveSupport::TestCase
GidJob.perform_later @person
assert_equal "Person with ID: 5", JobBuffer.last_value
end
+
+ test 'serialize includes current locale' do
+ assert_equal :en, HelloJob.new.serialize['locale']
+ end
+
+ test 'deserialize sets locale' do
+ job = HelloJob.new
+ job.deserialize 'locale' => :es
+ assert_equal :es, job.locale
+ end
+
+ test 'deserialize sets default locale' do
+ job = HelloJob.new
+ job.deserialize({})
+ assert_equal :en, job.locale
+ end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 04c4c446e2..8c60f037d2 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -165,6 +165,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_job_returns
+ job = assert_enqueued_with(job: LoggingJob) do
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ end
+
+ assert_instance_of LoggingJob, job
+ assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
+ assert_equal 'default', job.queue_name
+ assert_equal [1, 2, 3], job.arguments
+ end
+
def test_assert_enqueued_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_with(job: LoggingJob, queue: 'default') do
@@ -397,6 +408,17 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_performed_job_returns
+ job = assert_performed_with(job: NestedJob, queue: 'default') do
+ NestedJob.perform_later
+ end
+
+ assert_instance_of NestedJob, job
+ assert_nil job.scheduled_at
+ assert_equal [], job.arguments
+ assert_equal 'default', job.queue_name
+ end
+
def test_assert_performed_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
assert_performed_with(job: LoggingJob, at: Date.tomorrow.noon, queue: 'default') do
diff --git a/activejob/test/cases/translation_test.rb b/activejob/test/cases/translation_test.rb
new file mode 100644
index 0000000000..d5e3aaf9e3
--- /dev/null
+++ b/activejob/test/cases/translation_test.rb
@@ -0,0 +1,20 @@
+require 'helper'
+require 'jobs/translated_hello_job'
+
+class TranslationTest < ActiveSupport::TestCase
+ setup do
+ JobBuffer.clear
+ I18n.available_locales = [:en, :de]
+ @job = TranslatedHelloJob.new('Johannes')
+ end
+
+ teardown do
+ I18n.available_locales = [:en]
+ end
+
+ test 'it performs the job in the given locale' do
+ @job.locale = :de
+ @job.perform_now
+ assert_equal "Johannes says Guten Tag", JobBuffer.last_value
+ end
+end
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index d345092dee..ca8047ef0b 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -57,15 +57,31 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should supply a provider_job_id when available for immediate jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que)
+ skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que, :queue_classic)
test_job = TestJob.perform_later @id
- refute test_job.provider_job_id.nil?, 'Provider job id should be set by provider'
+ assert test_job.provider_job_id, 'Provider job id should be set by provider'
end
test 'should supply a provider_job_id when available for delayed jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :que)
+ skip unless adapter_is?(:delayed_job, :sidekiq, :que, :queue_classic)
delayed_test_job = TestJob.set(wait: 1.minute).perform_later @id
- refute delayed_test_job.provider_job_id.nil?,
- 'Provider job id should by set for delayed jobs by provider'
+ assert delayed_test_job.provider_job_id, 'Provider job id should by set for delayed jobs by provider'
+ end
+
+ test 'current locale is kept while running perform_later' do
+ skip if adapter_is?(:inline)
+
+ begin
+ I18n.available_locales = [:en, :de]
+ I18n.locale = :de
+
+ TestJob.perform_later @id
+ wait_for_jobs_to_finish_for(5.seconds)
+ assert job_executed
+ assert_equal 'de', job_output
+ ensure
+ I18n.available_locales = [:en]
+ I18n.locale = :en
+ end
end
end
diff --git a/activejob/test/jobs/translated_hello_job.rb b/activejob/test/jobs/translated_hello_job.rb
new file mode 100644
index 0000000000..9657cd3f54
--- /dev/null
+++ b/activejob/test/jobs/translated_hello_job.rb
@@ -0,0 +1,10 @@
+require_relative '../support/job_buffer'
+
+class TranslatedHelloJob < ActiveJob::Base
+ def perform(greeter = "David")
+ translations = { en: 'Hello', de: 'Guten Tag' }
+ hello = translations[I18n.locale]
+
+ JobBuffer.add("#{greeter} says #{hello}")
+ end
+end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index 4988cdb33f..d9938600f2 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -57,7 +57,7 @@ module SidekiqJobsManager
concurrency: 1,
timeout: 1,
})
- Sidekiq.poll_interval = 0.5
+ Sidekiq.average_scheduled_poll_interval = 0.5
Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
begin
sidekiq.run
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 09a68738ad..4ffdb8cffa 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -8,13 +8,17 @@ require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
JobsManager.current_manager.setup
CODE
+initializer 'i18n.rb', <<-CODE
+I18n.available_locales = [:en, :de]
+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
+ f.write I18n.locale
end
end
end
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index 7e87ede275..39aee6d407 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -45,4 +45,8 @@ module TestCaseHelpers
def job_executed
Dummy::Application.root.join("tmp/#{@id}").exist?
end
+
+ def job_output
+ File.read Dummy::Application.root.join("tmp/#{@id}")
+ end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index dddfd940bb..f25c901ecc 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -3,6 +3,10 @@
*Jay Elaraj*
+* Remove `ActiveModel::Serializers::Xml` from core.
+
+ *Zachary Scott*
+
* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
`ActiveModel::Dirty#[attr_name]_previous_change` to improve access
to recorded changes after the model has been saved.
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index d954467387..20414c1d61 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -155,7 +155,7 @@ behavior out of the box:
* Making objects serializable
<tt>ActiveModel::Serialization</tt> provides a standard interface for your object
- to provide +to_json+ or +to_xml+ serialization.
+ to provide +to_json+ serialization.
class SerialPerson
include ActiveModel::Serialization
@@ -177,13 +177,6 @@ behavior out of the box:
s = SerialPerson.new
s.to_json # => "{\"name\":null}"
- class SerialPerson
- include ActiveModel::Serializers::Xml
- end
-
- s = SerialPerson.new
- s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
{Learn more}[link:classes/ActiveModel/Serialization.html]
* Internationalization (i18n) support
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 8aa1b6f664..4e1b3f7495 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -58,7 +58,6 @@ module ActiveModel
eager_autoload do
autoload :JSON
- autoload :Xml
end
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 9033ec0dca..77e4ce3afe 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -342,7 +342,7 @@ module ActiveModel
private
# The methods +method_missing+ and +respond_to?+ of this module are
# invoked often in a typical rails, both of which invoke the method
- # +match_attribute_method?+. The latter method iterates through an
+ # +matched_attribute_method+. The latter method iterates through an
# array doing regular expression matches, which results in a lot of
# object creations. Most of the time it returns a +nil+ match. As the
# match result is always the same given a +method_name+, this cache is
@@ -429,7 +429,7 @@ module ActiveModel
if respond_to_without_attributes?(method, true)
super
else
- match = match_attribute_method?(method.to_s)
+ match = matched_attribute_method(method.to_s)
match ? attribute_missing(match, *args, &block) : super
end
end
@@ -454,7 +454,7 @@ module ActiveModel
# but found among all methods. Which means that the given method is private.
false
else
- !match_attribute_method?(method.to_s).nil?
+ !matched_attribute_method(method.to_s).nil?
end
end
@@ -466,7 +466,7 @@ module ActiveModel
private
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
- def match_attribute_method?(method_name)
+ def matched_attribute_method(method_name)
matches = self.class.send(:attribute_method_matchers_matching, method_name)
matches.detect { |match| attribute_method?(match.attr_name) }
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index f95849eb84..58d68d9620 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -40,7 +40,6 @@ module ActiveModel
#
# class Person
# include ActiveModel::Serializers::JSON
- # include ActiveModel::Serializers::Xml
#
# attr_accessor :name
#
@@ -55,13 +54,11 @@ module ActiveModel
# person.serializable_hash # => {"name"=>nil}
# person.as_json # => {"name"=>nil}
# person.to_json # => "{\"name\":null}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
# person.as_json # => {"name"=>"Bob"}
# person.to_json # => "{\"name\":\"Bob\"}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
# <tt>:include</tt>. The following are all valid examples:
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
deleted file mode 100644
index e33c766627..0000000000
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ /dev/null
@@ -1,238 +0,0 @@
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/array/conversions'
-require 'active_support/core_ext/hash/conversions'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/time/acts_like'
-
-module ActiveModel
- module Serializers
- # == \Active \Model XML Serializer
- module Xml
- extend ActiveSupport::Concern
- include ActiveModel::Serialization
-
- included do
- extend ActiveModel::Naming
- end
-
- class Serializer #:nodoc:
- class Attribute #:nodoc:
- attr_reader :name, :value, :type
-
- def initialize(name, serializable, value)
- @name, @serializable = name, serializable
-
- if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
- value = value.in_time_zone
- end
-
- @value = value
- @type = compute_type
- end
-
- def decorations
- decorations = {}
- decorations[:encoding] = 'base64' if type == :binary
- decorations[:type] = (type == :string) ? nil : type
- decorations[:nil] = true if value.nil?
- decorations
- end
-
- protected
-
- def compute_type
- return if value.nil?
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
- type ||= :string if value.respond_to?(:to_str)
- type ||= :yaml
- type
- end
- end
-
- class MethodAttribute < Attribute #:nodoc:
- end
-
- attr_reader :options
-
- def initialize(serializable, options = nil)
- @serializable = serializable
- @options = options ? options.dup : {}
- end
-
- def serializable_hash
- @serializable.serializable_hash(@options.except(:include))
- end
-
- def serializable_collection
- methods = Array(options[:methods]).map(&:to_s)
- serializable_hash.map do |name, value|
- name = name.to_s
- if methods.include?(name)
- self.class::MethodAttribute.new(name, @serializable, value)
- else
- self.class::Attribute.new(name, @serializable, value)
- end
- end
- end
-
- def serialize
- require 'builder' unless defined? ::Builder
-
- options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
-
- @builder = options[:builder]
- @builder.instruct! unless options[:skip_instruct]
-
- root = (options[:root] || @serializable.model_name.element).to_s
- root = ActiveSupport::XmlMini.rename_key(root, options)
-
- args = [root]
- args << { xmlns: options[:namespace] } if options[:namespace]
- args << { type: options[:type] } if options[:type] && !options[:skip_types]
-
- @builder.tag!(*args) do
- add_attributes_and_methods
- add_includes
- add_extra_behavior
- add_procs
- yield @builder if block_given?
- end
- end
-
- private
-
- def add_extra_behavior
- end
-
- def add_attributes_and_methods
- serializable_collection.each do |attribute|
- key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
- ActiveSupport::XmlMini.to_tag(key, attribute.value,
- options.merge(attribute.decorations))
- end
- end
-
- def add_includes
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
- add_associations(association, records, opts)
- end
- end
-
- # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
- def add_associations(association, records, opts)
- merged_options = opts.merge(options.slice(:builder, :indent))
- merged_options[:skip_instruct] = true
-
- [:skip_types, :dasherize, :camelize].each do |key|
- merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
- end
-
- if records.respond_to?(:to_ary)
- records = records.to_ary
-
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : { type: "array" }
- association_name = association.to_s.singularize
- merged_options[:root] = association_name
-
- if records.empty?
- @builder.tag!(tag, type)
- else
- @builder.tag!(tag, type) do
- records.each do |record|
- if options[:skip_types]
- record_type = {}
- else
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = { type: record_class }
- end
-
- record.to_xml merged_options.merge(record_type)
- end
- end
- end
- else
- merged_options[:root] = association.to_s
-
- unless records.class.to_s.underscore == association.to_s
- merged_options[:type] = records.class.name
- end
-
- records.to_xml merged_options
- end
- end
-
- def add_procs
- if procs = options.delete(:procs)
- Array(procs).each do |proc|
- if proc.arity == 1
- proc.call(options)
- else
- proc.call(options, @serializable)
- end
- end
- end
- end
- end
-
- # Returns XML representing the model. Configuration can be
- # passed through +options+.
- #
- # Without any +options+, the returned XML string will include all the
- # model's attributes.
- #
- # user = User.find(1)
- # user.to_xml
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <user>
- # <id type="integer">1</id>
- # <name>David</name>
- # <age type="integer">16</age>
- # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
- # </user>
- #
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
- # attributes included, and work similar to the +attributes+ method.
- #
- # To include the result of some method calls on the model use <tt>:methods</tt>.
- #
- # To include associations use <tt>:include</tt>.
- #
- # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
- def to_xml(options = {}, &block)
- Serializer.new(self, options).serialize(&block)
- end
-
- # Sets the model +attributes+ from an XML string. Returns +self+.
- #
- # class Person
- # include ActiveModel::Serializers::Xml
- #
- # attr_accessor :name, :age, :awesome
- #
- # def attributes=(hash)
- # hash.each do |key, value|
- # instance_variable_set("@#{key}", value)
- # end
- # end
- #
- # def attributes
- # instance_values
- # end
- # end
- #
- # xml = { name: 'bob', age: 22, awesome:true }.to_xml
- # person = Person.new
- # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
- # person.name # => "bob"
- # person.age # => 22
- # person.awesome # => true
- def from_xml(xml)
- self.attributes = Hash.from_xml(xml).values.first
- self
- end
- end
- end
-end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
deleted file mode 100644
index 37faf6cef8..0000000000
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ /dev/null
@@ -1,251 +0,0 @@
-require 'cases/helper'
-require 'models/contact'
-require 'active_support/core_ext/object/instance_variables'
-require 'ostruct'
-require 'yaml'
-
-module Admin
- class Contact < ::Contact
- end
-end
-
-class Customer < Struct.new(:name)
-end
-
-class Address
- include ActiveModel::Serializers::Xml
-
- attr_accessor :street, :city, :state, :zip, :apt_number
-
- def attributes
- instance_values
- end
-end
-
-class SerializableContact < Contact
- def serializable_hash(options={})
- super(options.merge(only: [:name, :age]))
- end
-end
-
-class XmlSerializationTest < ActiveModel::TestCase
- def setup
- @contact = Contact.new
- @contact.name = 'aaron stack'
- @contact.age = 25
- @contact.created_at = Time.utc(2006, 8, 1)
- @contact.awesome = false
- customer = Customer.new
- customer.name = "John"
- @contact.preferences = customer
- @contact.address = Address.new
- @contact.address.city = "Springfield"
- @contact.address.apt_number = 35
- @contact.friends = [Contact.new, Contact.new]
- @contact.contact = SerializableContact.new
- end
-
- test "should serialize default root" do
- xml = @contact.to_xml
- assert_match %r{^<contact>}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize namespaced root" do
- xml = Admin::Contact.new(@contact.attributes).to_xml
- assert_match %r{^<contact>}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize default root with namespace" do
- xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize custom root" do
- xml = @contact.to_xml root: 'xml_contact'
- assert_match %r{^<xml-contact>}, xml
- assert_match %r{</xml-contact>$}, xml
- end
-
- test "should allow undasherized tags" do
- xml = @contact.to_xml root: 'xml_contact', dasherize: false
- assert_match %r{^<xml_contact>}, xml
- assert_match %r{</xml_contact>$}, xml
- assert_match %r{<created_at}, xml
- end
-
- test "should allow camelized tags" do
- xml = @contact.to_xml root: 'xml_contact', camelize: true
- assert_match %r{^<XmlContact>}, xml
- assert_match %r{</XmlContact>$}, xml
- assert_match %r{<CreatedAt}, xml
- end
-
- test "should allow lower-camelized tags" do
- xml = @contact.to_xml root: 'xml_contact', camelize: :lower
- assert_match %r{^<xmlContact>}, xml
- assert_match %r{</xmlContact>$}, xml
- assert_match %r{<createdAt}, xml
- end
-
- test "should use serializable hash" do
- @contact = SerializableContact.new
- @contact.name = 'aaron stack'
- @contact.age = 25
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_match %r{<age type="integer">25</age>}, xml
- assert_no_match %r{<awesome>}, xml
- end
-
- test "should allow skipped types" do
- xml = @contact.to_xml skip_types: true
- assert_match %r{<age>25</age>}, xml
- end
-
- test "should include yielded additions" do
- xml_output = @contact.to_xml do |xml|
- xml.creator "David"
- end
- assert_match %r{<creator>David</creator>}, xml_output
- end
-
- test "should serialize string" do
- assert_match %r{<name>aaron stack</name>}, @contact.to_xml
- end
-
- test "should serialize nil" do
- assert_match %r{<pseudonyms nil="true"/>}, @contact.to_xml(methods: :pseudonyms)
- end
-
- test "should serialize integer" do
- assert_match %r{<age type="integer">25</age>}, @contact.to_xml
- end
-
- test "should serialize datetime" do
- assert_match %r{<created-at type="dateTime">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
- end
-
- test "should serialize boolean" do
- assert_match %r{<awesome type="boolean">false</awesome>}, @contact.to_xml
- end
-
- test "should serialize array" do
- assert_match %r{<social type="array">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social)
- end
-
- test "should serialize hash" do
- assert_match %r{<network>\s*<git type="symbol">github</git>\s*</network>}, @contact.to_xml(methods: :network)
- end
-
- test "should serialize yaml" do
- assert_match %r{<preferences type="yaml">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml
- end
-
- test "should call proc on object" do
- proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') }
- xml = @contact.to_xml(procs: [ proc ])
- assert_match %r{<nationality>unknown</nationality>}, xml
- end
-
- test "should supply serializable to second proc argument" do
- proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- xml = @contact.to_xml(procs: [ proc ])
- assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml
- end
-
- test "should serialize string correctly when type passed" do
- xml = @contact.to_xml type: 'Contact'
- assert_match %r{<contact type="Contact">}, xml
- assert_match %r{<name>aaron stack</name>}, xml
- end
-
- test "include option with singular association" do
- xml = @contact.to_xml include: :address, indent: 0
- assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
- end
-
- test "include option with plural association" do
- xml = @contact.to_xml include: :friends, indent: 0
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- class FriendList
- def initialize(friends)
- @friends = friends
- end
-
- def to_ary
- @friends
- end
- end
-
- test "include option with ary" do
- @contact.friends = FriendList.new(@contact.friends)
- xml = @contact.to_xml include: :friends, indent: 0
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- test "multiple includes" do
- xml = @contact.to_xml indent: 0, skip_instruct: true, include: [ :address, :friends ]
- assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- test "include with options" do
- xml = @contact.to_xml indent: 0, skip_instruct: true, include: { address: { only: :city } }
- assert xml.include?(%(><address><city>Springfield</city></address>))
- end
-
- test "propagates skip_types option to included associations" do
- xml = @contact.to_xml include: :friends, indent: 0, skip_types: true
- assert_match %r{<friends>}, xml
- assert_match %r{<friend>}, xml
- end
-
- test "propagates skip-types option to included associations and attributes" do
- xml = @contact.to_xml skip_types: true, include: :address, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number>}, xml
- end
-
- test "propagates camelize option to included associations and attributes" do
- xml = @contact.to_xml camelize: true, include: :address, indent: 0
- assert_match %r{<Address>}, xml
- assert_match %r{<AptNumber type="integer">}, xml
- end
-
- test "propagates dasherize option to included associations and attributes" do
- xml = @contact.to_xml dasherize: false, include: :address, indent: 0
- assert_match %r{<apt_number type="integer">}, xml
- end
-
- test "don't propagate skip_types if skip_types is defined at the included association level" do
- xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "don't propagate camelize if camelize is defined at the included association level" do
- xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "don't propagate dasherize if dasherize is defined at the included association level" do
- xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "association with sti" do
- xml = @contact.to_xml(include: :contact)
- assert xml.include?(%(<contact type="SerializableContact">))
- end
-end
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index bcfd267a34..113ab0bc1f 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -4,7 +4,6 @@ class Contact
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
- include ActiveModel::Serializers::Xml
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
attr_accessor :address, :friends, :contact
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 5f273d53f2..0841b2f679 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,65 @@
+* Uniqueness validator raises descriptive error when running on a persisted
+ record without primary key.
+
+ Closes #21304.
+
+ *Yves Senn*
+
+* Add a native JSON data type support in MySQL.
+
+ Example:
+
+ create_table :json_data_type do |t|
+ t.json :settings
+ end
+
+ *Ryuta Kamizono*
+
+* Descriptive error message when fixtures contain a missing column.
+
+ Closes #21201.
+
+ *Yves Senn*
+
+* `ActiveRecord::Tasks::PostgreSQLDatabaseTasks` fail if shellout to
+ postgresql commands (like `pg_dump`) is not successful.
+
+ *Bryan Paxton*, *Nate Berkopec*
+
+* Add `ActiveRecord::Relation#in_batches` to work with records and relations
+ in batches.
+
+ Available options are `of` (batch size), `load`, `begin_at`, and `end_at`.
+
+ Examples:
+
+ Person.in_batches.each_record(&:party_all_night!)
+ Person.in_batches.update_all(awesome: true)
+ Person.in_batches.delete_all
+ Person.in_batches.each do |relation|
+ relation.delete_all
+ sleep 10 # Throttles the delete queries
+ end
+
+ Closes #20933.
+
+ *Sina Siadat*
+
+* Added methods for PostgreSQL geometric data types to use in migrations
+
+ Example:
+
+ create_table :foo do |t|
+ t.line :foo_line
+ t.lseg :foo_lseg
+ t.box :foo_box
+ t.path :foo_path
+ t.polygon :foo_polygon
+ t.circle :foo_circle
+ end
+
+ *Mehmet Emin İNAÇ*
+
* Add `cache_key` to ActiveRecord::Relation.
Example:
@@ -311,6 +373,10 @@
*Ryuta Kamizono*
+* Remove `ActiveRecord::Serialization::XmlSerializer` from core.
+
+ *Zachary Scott*
+
* Make `unscope` aware of "less than" and "greater than" conditions.
*TAKAHASHI Kazuaki*
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 7c729676a7..c7b396f3d4 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -251,6 +251,14 @@ module ActiveRecord
initialize_attributes(record)
end
end
+
+ # Returns true if statement cache should be skipped on the association reader.
+ def skip_statement_cache?
+ reflection.scope_chain.any?(&:any?) ||
+ scope.eager_loading? ||
+ klass.scope_attributes? ||
+ reflection.source_reflection.active_record.default_scopes.any?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index ffd9c9d6fc..b18d99d54e 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -46,7 +46,7 @@ module ActiveRecord::Associations::Builder
join_model = Class.new(ActiveRecord::Base) {
class << self;
- attr_accessor :class_resolver
+ attr_accessor :left_model
attr_accessor :name
attr_accessor :table_name_resolver
attr_accessor :left_reflection
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
end
def self.compute_type(class_name)
- class_resolver.compute_type class_name
+ left_model.compute_type class_name
end
def self.add_left_association(name, options)
@@ -72,11 +72,15 @@ module ActiveRecord::Associations::Builder
self.right_reflection = _reflect_on_association(rhs_name)
end
+ def self.retrieve_connection
+ left_model.retrieve_connection
+ end
+
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
join_model.table_name_resolver = habtm
- join_model.class_resolver = lhs_model
+ join_model.left_model = lhs_model
join_model.add_left_association :left_side, anonymous_class: lhs_model
join_model.add_right_association association_name, belongs_to_options(options)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 87576abd92..256df3ca11 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -62,8 +62,10 @@ module ActiveRecord
record.send(reflection.association_primary_key)
end
else
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- scope.pluck(column)
+ @association_ids ||= (
+ column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
+ scope.pluck(column)
+ )
end
end
@@ -440,12 +442,7 @@ module ActiveRecord
private
def get_records
- if reflection.scope_chain.any?(&:any?) ||
- scope.eager_loading? ||
- klass.scope_attributes?
-
- return scope.to_a
- end
+ return scope.to_a if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 9f6c832c1b..19cdd7f470 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -87,8 +87,15 @@ module ActiveRecord
[association_scope.limit_value, count].compact.min
end
+
+ # Returns whether a counter cache should be used for this association.
+ #
+ # The counter_cache option must be given on either the owner or inverse
+ # association, and the column must be present on the owner.
def has_cached_counter?(reflection = reflection())
- owner.attribute_present?(cached_counter_attribute_name(reflection))
+ if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
+ end
end
def cached_counter_attribute_name(reflection = reflection())
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 6ecc741195..3992a240b9 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -116,7 +116,7 @@ module ActiveRecord
when String
preloaders_for_one(association.to_sym, records, scope)
else
- raise ArgumentError, "#{association.inspect} was not recognised for preload"
+ raise ArgumentError, "#{association.inspect} was not recognized for preload"
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 30c5d72482..03cb8cb8c3 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -47,12 +47,7 @@ module ActiveRecord
end
def get_records
- if reflection.scope_chain.any?(&:any?) ||
- scope.eager_loading? ||
- klass.scope_attributes?
-
- return scope.limit(1).to_a
- end
+ return scope.limit(1).to_a if skip_statement_cache?
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 7fb899c242..0862306749 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -385,27 +385,27 @@ module ActiveRecord
#
# For example:
#
- # class PostsController < ActionController::Base
- # after_action :print_accessed_fields, only: :index
+ # class PostsController < ActionController::Base
+ # after_action :print_accessed_fields, only: :index
#
- # def index
- # @posts = Post.all
- # end
+ # def index
+ # @posts = Post.all
+ # end
#
- # private
+ # private
#
- # def print_accessed_fields
- # p @posts.first.accessed_fields
+ # def print_accessed_fields
+ # p @posts.first.accessed_fields
+ # end
# end
- # end
#
# Which allows you to quickly change your code to:
#
- # class PostsController < ActionController::Base
- # def index
- # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # class PostsController < ActionController::Base
+ # def index
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # end
# end
- # end
def accessed_fields
@attributes.accessed
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index e03bf5945d..60eecab0d0 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# serialized object must be of that class on assignment and retrieval.
# Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
#
- # Empty objects as +{}+, in the case of +Hash+, or +[]+, in the case of
+ # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
# +Array+, will always be persisted as null.
#
# Keep in mind that database adapters handle certain serialization tasks
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index dbb0e2fab2..d0de42d27c 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -234,7 +234,7 @@ module ActiveRecord
super
end
- # Marks this record to be destroyed as part of the parents save transaction.
+ # Marks this record to be destroyed as part of the parent's save transaction.
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
# when <tt>parent.save</tt> is called.
#
@@ -243,7 +243,7 @@ module ActiveRecord
@marked_for_destruction = true
end
- # Returns whether or not this record will be destroyed as part of the parents save transaction.
+ # Returns whether or not this record will be destroyed as part of the parent's save transaction.
#
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
def marked_for_destruction?
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index c7c769b283..ccdbebbc77 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -206,7 +206,8 @@ module ActiveRecord
# == Ordering callbacks
#
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
- # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the
+ # <tt>dependent: destroy</tt> option.
#
# Let's look at the code below:
#
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 38dd9578fe..1e13b24867 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -289,8 +289,12 @@ module ActiveRecord
columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
- type = lookup_cast_type_from_column(columns[name])
- Relation::QueryAttribute.new(name, value, type)
+ if column = columns[name]
+ type = lookup_cast_type_from_column(column)
+ Relation::QueryAttribute.new(name, value, type)
+ else
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".)
+ end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
value_list = prepare_binds_for_database(binds).map do |value|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index d17e272ed1..3115e03ea2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -214,6 +214,7 @@ module ActiveRecord
@name = name
end
+ # Returns an array of ColumnDefinition objects for the columns of the table.
def columns; @columns_hash.values; end
# Returns a ColumnDefinition for the column with name +name+.
@@ -369,6 +370,8 @@ module ActiveRecord
self
end
+ # remove the column +name+ from the table.
+ # remove_column(:account_id)
def remove_column(name)
@columns_hash.delete name.to_s
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 56227ddd80..ed14c781c6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -266,6 +266,11 @@ module ActiveRecord
false
end
+ # Does this adapter support json data type?
+ def supports_json?
+ 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 af156c9c78..96ea866580 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -10,6 +10,10 @@ module ActiveRecord
options[:auto_increment] = true if type == :bigint
super
end
+
+ def json(*args, **options)
+ args.each { |name| column(name, :json, options) }
+ end
end
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
@@ -242,17 +246,19 @@ module ActiveRecord
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) auto_increment PRIMARY KEY",
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "int", :limit => 4 },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
+ primary_key: "int(11) auto_increment PRIMARY KEY",
+ string: { name: "varchar", limit: 255 },
+ text: { name: "text" },
+ integer: { name: "int", limit: 4 },
+ float: { name: "float" },
+ decimal: { name: "decimal" },
+ datetime: { name: "datetime" },
+ time: { name: "time" },
+ date: { name: "date" },
+ binary: { name: "blob" },
+ boolean: { name: "tinyint", limit: 1 },
+ bigint: { name: "bigint" },
+ json: { name: "json" },
}
INDEX_TYPES = [:fulltext, :spatial]
@@ -790,6 +796,7 @@ module ActiveRecord
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
+ m.register_type %r(^json)i, MysqlJson.new
register_integer_type m, %r(^bigint)i, limit: 8
register_integer_type m, %r(^int)i, limit: 4
@@ -1043,6 +1050,14 @@ module ActiveRecord
end
end
+ class MysqlJson < Type::Json # :nodoc:
+ def changed_in_place?(raw_old_value, new_value)
+ # Normalization is required because MySQL JSON data format includes
+ # the space between the elements.
+ super(serialize(deserialize(raw_old_value)), new_value)
+ end
+ end
+
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
@@ -1063,6 +1078,8 @@ module ActiveRecord
end
end
+ ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql)
+ ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index e97e82f056..ff43c7ec42 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -41,6 +41,10 @@ module ActiveRecord
true
end
+ def supports_json?
+ version >= '5.7.8'
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
@@ -254,7 +258,7 @@ module ActiveRecord
end
def full_version
- @full_version ||= @connection.info[:version]
+ @full_version ||= @connection.server_info[:version]
end
def set_field_encoding field_name
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 68752cdd80..37226831dc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -8,7 +8,6 @@ require 'active_record/connection_adapters/postgresql/oid/decimal'
require 'active_record/connection_adapters/postgresql/oid/enum'
require 'active_record/connection_adapters/postgresql/oid/hstore'
require 'active_record/connection_adapters/postgresql/oid/inet'
-require 'active_record/connection_adapters/postgresql/oid/json'
require 'active_record/connection_adapters/postgresql/oid/jsonb'
require 'active_record/connection_adapters/postgresql/oid/money'
require 'active_record/connection_adapters/postgresql/oid/point'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
deleted file mode 100644
index 8e1256baad..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Json < Type::Value # :nodoc:
- include Type::Helpers::Mutable
-
- def type
- :json
- end
-
- def deserialize(value)
- if value.is_a?(::String)
- ::ActiveSupport::JSON.decode(value) rescue nil
- else
- value
- end
- end
-
- def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
- end
-
- def accessor
- ActiveRecord::Store::StringKeyedHashAccessor
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index 87391b5dc7..1f6d63582c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -2,7 +2,7 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
- class Jsonb < Json # :nodoc:
+ class Jsonb < Type::Json # :nodoc:
def type
:jsonb
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 022dbdfa27..6399bddbee 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -103,6 +103,30 @@ module ActiveRecord
args.each { |name| column(name, :point, options) }
end
+ def line(*args, **options)
+ args.each { |name| column(name, :line, options) }
+ end
+
+ def lseg(*args, **options)
+ args.each { |name| column(name, :lseg, options) }
+ end
+
+ def box(*args, **options)
+ args.each { |name| column(name, :box, options) }
+ end
+
+ def path(*args, **options)
+ args.each { |name| column(name, :path, options) }
+ end
+
+ def polygon(*args, **options)
+ args.each { |name| column(name, :polygon, options) }
+ end
+
+ def circle(*args, **options)
+ args.each { |name| column(name, :circle, options) }
+ end
+
def serial(*args, **options)
args.each { |name| column(name, :serial, options) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2c43c46a3d..861edbf3a2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -201,6 +201,10 @@ module ActiveRecord
true
end
+ def supports_json?
+ postgresql_version >= 90200
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -478,7 +482,7 @@ module ActiveRecord
m.register_type 'bytea', OID::Bytea.new
m.register_type 'point', OID::Point.new
m.register_type 'hstore', OID::Hstore.new
- m.register_type 'json', OID::Json.new
+ m.register_type 'json', Type::Json.new
m.register_type 'jsonb', OID::Jsonb.new
m.register_type 'cidr', OID::Cidr.new
m.register_type 'inet', OID::Inet.new
@@ -834,7 +838,6 @@ module ActiveRecord
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
- ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index d589620f8a..718f04871d 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -218,11 +218,12 @@ module ActiveRecord
class UnknownPrimaryKey < ActiveRecordError
attr_reader :model
- def initialize(model)
- super("Unknown primary key for table #{model.table_name} in model #{model}.")
+ def initialize(model, description = nil)
+ message = "Unknown primary key for table #{model.table_name} in model #{model}."
+ message += "\n#{description}" if description
+ super(message)
@model = model
end
-
end
# Raised when a relation cannot be mutated because it's already loaded.
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4cfda302ea..c35efbdee8 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -376,6 +376,7 @@ module ActiveRecord
attr_accessor :delegate # :nodoc:
attr_accessor :disable_ddl_transaction # :nodoc:
+ # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
def check_pending!(connection = Base.connection)
raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
end
@@ -408,7 +409,10 @@ module ActiveRecord
new.migrate direction
end
- # Disable DDL transactions for this migration.
+ # Disable the transaction wrapping this migration.
+ # You can still create your own transactions even after calling #disable_ddl_transaction!
+ #
+ # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration].
def disable_ddl_transaction!
@disable_ddl_transaction = true
end
@@ -716,7 +720,9 @@ module ActiveRecord
end
end
- def table_name_options(config = ActiveRecord::Base)
+ # Builds a hash for use in ActiveRecord::Migration#proper_table_name using
+ # the Active Record object's table_name prefix and suffix
+ def table_name_options(config = ActiveRecord::Base) #:nodoc:
{
table_name_prefix: config.table_name_prefix,
table_name_suffix: config.table_name_suffix
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 4e597590e9..87a1988f2f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -6,7 +6,7 @@ module ActiveRecord
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
- delegate :find_each, :find_in_batches, to: :all
+ delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index da6b8447d3..6dd54f9262 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,11 +16,11 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::QueryCache"
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
+ ActiveRecord::QueryCache
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
+ ActiveRecord::ConnectionAdapters::ConnectionManagement
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
@@ -78,8 +78,8 @@ module ActiveRecord
initializer "active_record.migration_error" do
if config.active_record.delete(:migration_error) == :page_load
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::Migration::CheckPending"
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
+ ActiveRecord::Migration::CheckPending
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 3ed04dee3b..e47b7b1ed9 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -667,6 +667,13 @@ module ActiveRecord
"#<#{self.class.name} [#{entries.join(', ')}]>"
end
+ protected
+
+ def load_records(records)
+ @records = records
+ @loaded = true
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index e07580a563..beb8fa511c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,3 +1,5 @@
+require "active_record/relation/batches/batch_enumerator"
+
module ActiveRecord
module Batches
# Looping through a collection of records from the database
@@ -122,24 +124,102 @@ module ActiveRecord
end
end
+ in_batches(of: batch_size, begin_at: begin_at, end_at: end_at, load: true) do |batch|
+ yield batch.to_a
+ end
+ end
+
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
+ #
+ # Person.where("age > 21").in_batches do |relation|
+ # relation.delete_all
+ # sleep(10) # Throttle the delete queries
+ # end
+ #
+ # If you do not provide a block to #in_batches, it will return a
+ # BatchEnumerator which is enumerable.
+ #
+ # Person.in_batches.with_index do |relation, batch_index|
+ # puts "Processing relation ##{batch_index}"
+ # relation.each { |relation| relation.delete_all }
+ # end
+ #
+ # Examples of calling methods on the returned BatchEnumerator object:
+ #
+ # Person.in_batches.delete_all
+ # Person.in_batches.update_all(awesome: true)
+ # Person.in_batches.each_record(&:party_all_night!)
+ #
+ # ==== Options
+ # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
+ # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ #
+ # This is especially useful if you want to work with the
+ # ActiveRecord::Relation object instead of the array of records, or if
+ # you want multiple workers dealing with the same processing queue. You can
+ # make worker 1 handle all the records between id 0 and 10,000 and worker 2
+ # handle from 10,000 and beyond (by setting the +:begin_at+ and +:end_at+
+ # option on each worker).
+ #
+ # # Let's process the next 2000 records
+ # Person.in_batches(of: 2000, begin_at: 2000).update_all(awesome: true)
+ #
+ # An example of calling where query method on the relation:
+ #
+ # Person.in_batches.each do |relation|
+ # relation.update_all('age = age + 1')
+ # relation.where('age > 21').update_all(should_party: true)
+ # relation.where('age <= 21').delete_all
+ # end
+ #
+ # NOTE: If you are going to iterate through each record, you should call
+ # #each_record on the yielded BatchEnumerator:
+ #
+ # Person.in_batches.each_record(&:party_all_night!)
+ #
+ # NOTE: It's not possible to set the order. That is automatically set to
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # consistent. Therefore the primary key must be orderable, e.g an integer
+ # or a string.
+ #
+ # NOTE: You can't set the limit either, that's used to control the batch
+ # sizes.
+ def in_batches(of: 1000, begin_at: nil, end_at: nil, load: false)
+ relation = self
+ unless block_given?
+ return BatchEnumerator.new(of: of, begin_at: begin_at, end_at: end_at, relation: self)
+ end
+
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- relation = relation.reorder(batch_order).limit(batch_size)
+ relation = relation.reorder(batch_order).limit(of)
relation = apply_limits(relation, begin_at, end_at)
- records = relation.to_a
+ batch_relation = relation
+
+ loop do
+ if load
+ records = batch_relation.to_a
+ ids = records.map(&:id)
+ yielded_relation = self.where(primary_key => ids)
+ yielded_relation.load_records(records)
+ else
+ ids = batch_relation.pluck(primary_key)
+ yielded_relation = self.where(primary_key => ids)
+ end
- while records.any?
- records_size = records.size
- primary_key_offset = records.last.id
- raise "Primary key not included in the custom select clause" unless primary_key_offset
+ break if ids.empty?
- yield records
+ primary_key_offset = ids.last
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
- break if records_size < batch_size
+ yield yielded_relation
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
+ break if ids.length < of
+ batch_relation = relation.where(table[primary_key].gt(primary_key_offset))
end
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
new file mode 100644
index 0000000000..153aae9584
--- /dev/null
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -0,0 +1,67 @@
+module ActiveRecord
+ module Batches
+ class BatchEnumerator
+ include Enumerable
+
+ def initialize(of: 1000, begin_at: nil, end_at: nil, relation:) #:nodoc:
+ @of = of
+ @relation = relation
+ @begin_at = begin_at
+ @end_at = end_at
+ end
+
+ # Looping through a collection of records from the database (using the
+ # +all+ method, for example) is very inefficient since it will try to
+ # instantiate all the objects at once.
+ #
+ # In that case, batch processing methods allow you to work with the
+ # records in batches, thereby greatly reducing memory consumption.
+ #
+ # Person.in_batches.each_record do |person|
+ # person.do_awesome_stuff
+ # end
+ #
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
+ # person.party_all_night!
+ # end
+ #
+ # If you do not provide a block to #each_record, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # Person.in_batches.each_record.with_index do |person, index|
+ # person.award_trophy(index + 1)
+ # end
+ def each_record
+ return to_enum(:each_record) unless block_given?
+
+ @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: true).each do |relation|
+ relation.to_a.each { |record| yield record }
+ end
+ end
+
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
+ #
+ # People.in_batches.delete_all
+ # People.in_batches.destroy_all('age < 10')
+ # People.in_batches.update_all('age = age + 1')
+ [:delete_all, :update_all, :destroy_all].each do |method|
+ define_method(method) do |*args, &block|
+ @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false).each do |relation|
+ relation.send(method, *args, &block)
+ end
+ end
+ end
+
+ # Yields an ActiveRecord::Relation object for each batch of records.
+ #
+ # Person.in_batches.each do |relation|
+ # relation.update_all(awesome: true)
+ # end
+ def each
+ enum = @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false)
+ return enum.each { |relation| yield relation } if block_given?
+ enum
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 3590b8846e..a1adf8e3ee 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -100,6 +100,7 @@ module ActiveRecord
end
def build_default_scope(base_rel = relation) # :nodoc:
+ return if abstract_class?
if !Base.is_a?(method(:default_scope).owner)
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 48c12dcf9f..23dc6465af 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -18,5 +18,3 @@ module ActiveRecord #:nodoc:
end
end
end
-
-require 'active_record/serializers/xml_serializer'
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
deleted file mode 100644
index 89b7e0be82..0000000000
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-require 'active_support/core_ext/hash/conversions'
-
-module ActiveRecord #:nodoc:
- module Serialization
- include ActiveModel::Serializers::Xml
-
- # Builds an XML document to represent the model. Some configuration is
- # available through +options+. However more complicated cases should
- # override ActiveRecord::Base#to_xml.
- #
- # By default the generated XML document will include the processing
- # instruction and all the object's attributes. For example:
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <topic>
- # <title>The First Topic</title>
- # <author-name>David</author-name>
- # <id type="integer">1</id>
- # <approved type="boolean">false</approved>
- # <replies-count type="integer">0</replies-count>
- # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
- # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
- # <content>Have a nice day</content>
- # <author-email-address>david@loudthinking.com</author-email-address>
- # <parent-id></parent-id>
- # <last-read type="date">2004-04-15</last-read>
- # </topic>
- #
- # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
- # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
- # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
- # +attributes+ method. The default is to dasherize all column names, but you
- # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
- # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
- # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
- #
- # For instance:
- #
- # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
- #
- # <topic>
- # <title>The First Topic</title>
- # <author-name>David</author-name>
- # <approved type="boolean">false</approved>
- # <content>Have a nice day</content>
- # <author-email-address>david@loudthinking.com</author-email-address>
- # <parent-id></parent-id>
- # <last-read type="date">2004-04-15</last-read>
- # </topic>
- #
- # To include first level associations use <tt>:include</tt>:
- #
- # firm.to_xml include: [ :account, :clients ]
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <firm>
- # <id type="integer">1</id>
- # <rating type="integer">1</rating>
- # <name>37signals</name>
- # <clients type="array">
- # <client>
- # <rating type="integer">1</rating>
- # <name>Summit</name>
- # </client>
- # <client>
- # <rating type="integer">1</rating>
- # <name>Microsoft</name>
- # </client>
- # </clients>
- # <account>
- # <id type="integer">1</id>
- # <credit-limit type="integer">50</credit-limit>
- # </account>
- # </firm>
- #
- # Additionally, the record being serialized will be passed to a Proc's second
- # parameter. This allows for ad hoc additions to the resultant document that
- # incorporate the context of the record being serialized. And by leveraging the
- # closure created by a Proc, to_xml can be used to add elements that normally fall
- # outside of the scope of the model -- for example, generating and appending URLs
- # associated with models.
- #
- # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- # firm.to_xml procs: [ proc ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <name-reverse>slangis73</name-reverse>
- # </firm>
- #
- # To include deeper levels of associations pass a hash like this:
- #
- # firm.to_xml include: {account: {}, clients: {include: :address}}
- # <?xml version="1.0" encoding="UTF-8"?>
- # <firm>
- # <id type="integer">1</id>
- # <rating type="integer">1</rating>
- # <name>37signals</name>
- # <clients type="array">
- # <client>
- # <rating type="integer">1</rating>
- # <name>Summit</name>
- # <address>
- # ...
- # </address>
- # </client>
- # <client>
- # <rating type="integer">1</rating>
- # <name>Microsoft</name>
- # <address>
- # ...
- # </address>
- # </client>
- # </clients>
- # <account>
- # <id type="integer">1</id>
- # <credit-limit type="integer">50</credit-limit>
- # </account>
- # </firm>
- #
- # To include any methods on the model being called use <tt>:methods</tt>:
- #
- # firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <calculated-earnings>100000000000000000</calculated-earnings>
- # <real-earnings>5</real-earnings>
- # </firm>
- #
- # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
- # modified version of the options hash that was given to +to_xml+:
- #
- # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
- # firm.to_xml procs: [ proc ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <abc>def</abc>
- # </firm>
- #
- # Alternatively, you can yield the builder object as part of the +to_xml+ call:
- #
- # firm.to_xml do |xml|
- # xml.creator do
- # xml.first_name "David"
- # xml.last_name "Heinemeier Hansson"
- # end
- # end
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <creator>
- # <first_name>David</first_name>
- # <last_name>Heinemeier Hansson</last_name>
- # </creator>
- # </firm>
- #
- # As noted above, you may override +to_xml+ in your ActiveRecord::Base
- # subclasses to have complete control about what's generated. The general
- # form of doing this is:
- #
- # class IHaveMyOwnXML < ActiveRecord::Base
- # def to_xml(options = {})
- # require 'builder'
- # options[:indent] ||= 2
- # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
- # xml.instruct! unless options[:skip_instruct]
- # xml.level_one do
- # xml.tag!(:second_level, 'content')
- # end
- # end
- # end
- def to_xml(options = {}, &block)
- XmlSerializer.new(self, options).serialize(&block)
- end
- end
-
- class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
- def compute_type
- klass = @serializable.class
- cast_type = klass.type_for_attribute(name)
-
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type
-
- { :text => :string,
- :time => :datetime }[type] || type
- end
- protected :compute_type
- end
- end
-end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index d7da95c8a9..55f839444b 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -1,5 +1,3 @@
-require 'shellwords'
-
module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
@@ -55,19 +53,22 @@ module ActiveRecord
when String
ActiveRecord::Base.dump_schemas
end
+
+ args = ['-i', '-s', '-x', '-O', '-f', filename]
unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
+ args << search_path.split(',').map do |part|
+ "--schema=#{part.strip}"
+ end.join(' ')
end
-
- command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
- raise 'Error dumping database' unless Kernel.system(command)
-
+ args << configuration['database']
+ run_cmd('pg_dump', args, 'dumping')
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
def structure_load(filename)
set_psql_env
- Kernel.system("psql -X -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
+ args = [ '-q', '-f', filename, configuration['database'] ]
+ run_cmd('psql', args, 'loading' )
end
private
@@ -93,6 +94,17 @@ module ActiveRecord
ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
end
+
+ def run_cmd(cmd, args, action)
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
+ end
+
+ def run_cmd_error(cmd, args, action)
+ msg = "failed to execute:\n"
+ msg << "#{cmd} #{args.join(' ')}\n\n"
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
+ msg
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 2c0cda69d0..8f56a37f3c 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -10,6 +10,7 @@ require 'active_record/type/decimal'
require 'active_record/type/decimal_without_scale'
require 'active_record/type/float'
require 'active_record/type/integer'
+require 'active_record/type/json'
require 'active_record/type/serialized'
require 'active_record/type/string'
require 'active_record/type/text'
@@ -59,6 +60,7 @@ module ActiveRecord
register(:decimal, Type::Decimal, override: false)
register(:float, Type::Float, override: false)
register(:integer, Type::Integer, override: false)
+ register(:json, Type::Json, override: false)
register(:string, Type::String, override: false)
register(:text, Type::Text, override: false)
register(:time, Type::Time, override: false)
diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb
new file mode 100644
index 0000000000..1728bd3a8e
--- /dev/null
+++ b/activerecord/lib/active_record/type/json.rb
@@ -0,0 +1,31 @@
+module ActiveRecord
+ module Type
+ class Json < Type::Value # :nodoc:
+ include Type::Helpers::Mutable
+
+ def type
+ :json
+ end
+
+ def deserialize(value)
+ if value.is_a?(::String)
+ ::ActiveSupport::JSON.decode(value) rescue nil
+ else
+ value
+ end
+ end
+
+ def serialize(value)
+ if value.is_a?(::Array) || value.is_a?(::Hash)
+ ::ActiveSupport::JSON.encode(value)
+ else
+ value
+ end
+ end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 32d17a1392..5706bbd903 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -18,7 +18,11 @@ module ActiveRecord
relation = build_relation(finder_class, table, attribute, value)
if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
- relation = relation.where.not(finder_class.primary_key => record.id)
+ if finder_class.primary_key
+ relation = relation.where.not(finder_class.primary_key => record.id)
+ else
+ raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
+ end
end
relation = scope_relation(record, table, relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index 426b088e2f..2024aa36ab 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -24,6 +24,6 @@ class MysqlQuotingTest < ActiveRecord::MysqlTestCase
@conn.stubs(:full_version).returns('5.6.3')
@conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
t = Time.now.change(usec: 1)
- refute_match(/\.000001\z/, @conn.quoted_date(t))
+ assert_no_match(/\.000001\z/, @conn.quoted_date(t))
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
new file mode 100644
index 0000000000..c8c933af5e
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -0,0 +1,172 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_json?
+class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ class JsonDataType < ActiveRecord::Base
+ self.table_name = 'json_data_type'
+
+ store_accessor :settings, :resolution
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.create_table('json_data_type') do |t|
+ t.json 'payload'
+ t.json 'settings'
+ end
+ end
+ end
+
+ def teardown
+ @connection.drop_table :json_data_type, if_exists: true
+ JsonDataType.reset_column_information
+ end
+
+ def test_column
+ column = JsonDataType.columns_hash["payload"]
+ assert_equal :json, column.type
+ assert_equal 'json', column.sql_type
+
+ type = JsonDataType.type_for_attribute("payload")
+ assert_not type.binary?
+ end
+
+ def test_change_table_supports_json
+ @connection.change_table('json_data_type') do |t|
+ t.json 'users'
+ end
+ JsonDataType.reset_column_information
+ column = JsonDataType.columns_hash['users']
+ assert_equal :json, column.type
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("json_data_type")
+ assert_match(/t\.json\s+"settings"/, output)
+ end
+
+ def test_cast_value_on_write
+ x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar}
+ assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast)
+ assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload)
+ x.save
+ assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload)
+ end
+
+ def test_type_cast_json
+ type = JsonDataType.type_for_attribute("payload")
+
+ data = "{\"a_key\":\"a_value\"}"
+ hash = type.deserialize(data)
+ assert_equal({'a_key' => 'a_value'}, hash)
+ assert_equal({'a_key' => 'a_value'}, type.deserialize(data))
+
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({'key'=>nil}, type.deserialize('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
+ end
+
+ def test_rewrite
+ @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
+ x = JsonDataType.first
+ x.payload = { '"a\'' => 'b' }
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
+ x = JsonDataType.first
+ assert_equal({'k' => 'v'}, x.payload)
+ end
+
+ def test_select_multikey
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
+ x = JsonDataType.first
+ assert_equal({'k1' => 'v1', 'k2' => 'v2', 'k3' => [1,2,3]}, x.payload)
+ end
+
+ def test_null_json
+ @connection.execute %q|insert into json_data_type (payload) VALUES(null)|
+ x = JsonDataType.first
+ assert_equal(nil, x.payload)
+ end
+
+ def test_select_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ assert_equal(['v0', {'k1' => 'v1'}], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
+ x = JsonDataType.first
+ x.payload = ['v1', {'k2' => 'v2'}, 'v3']
+ assert x.save!
+ end
+
+ def test_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ x.save!
+ x = JsonDataType.first
+ assert_equal "320×480", x.resolution
+
+ x.resolution = "640×1136"
+ x.save!
+
+ x = JsonDataType.first
+ assert_equal "640×1136", x.resolution
+ end
+
+ def test_duplication_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = x.dup
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_changes_in_place
+ json = JsonDataType.new
+ assert_not json.changed?
+
+ json.payload = { 'one' => 'two' }
+ assert json.changed?
+ assert json.payload_changed?
+
+ json.save!
+ assert_not json.changed?
+
+ json.payload['three'] = 'four'
+ assert json.payload_changed?
+
+ json.save!
+ json.reload
+
+ assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload)
+ assert_not json.changed?
+ end
+
+ def test_assigning_invalid_json
+ json = JsonDataType.new
+
+ json.payload = 'foo'
+
+ assert_nil json.payload
+ end
+end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
index 92221e61bf..2de7e1b526 100644
--- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
@@ -16,6 +16,6 @@ class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
@connection.stubs(:full_version).returns('5.6.3')
@connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
t = Time.now.change(usec: 1)
- refute_match(/\.000001\z/, @connection.quoted_date(t))
+ assert_no_match(/\.000001\z/, @connection.quoted_date(t))
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 73cde05504..0d2e2264e3 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -21,6 +21,9 @@ require 'models/column'
require 'models/record'
require 'models/admin'
require 'models/admin/user'
+require 'models/ship'
+require 'models/treasure'
+require 'models/parrot'
class BelongsToAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -91,7 +94,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- refute account.valid?
+ assert_not account.valid?
assert_equal [{error: :blank}], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -108,7 +111,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- refute account.valid?
+ assert_not account.valid?
assert_equal [{error: :blank}], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -347,6 +350,22 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Company.all.merge!(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size
end
+ def test_belongs_to_without_counter_cache_option
+ # Ship has a conventionally named `treasures_count` column, but the counter_cache
+ # option is not given on the association.
+ ship = Ship.create(name: 'Countless')
+
+ assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do
+ treasure = Treasure.new(name: 'Gold', ship: ship)
+ treasure.save
+ end
+
+ assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed unless counter_cache is given on the relation" do
+ treasure = ship.treasures.first
+ treasure.destroy
+ end
+ end
+
def test_belongs_to_counter
debate = Topic.create("title" => "debate")
assert_equal 0, debate.read_attribute("replies_count"), "No replies yet"
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ffbf60e390..8ed7b4e39f 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1325,6 +1325,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_match message, error.message
end
+ test "preload with invalid argument" do
+ exception = assert_raises(ArgumentError) do
+ Author.preload(10).to_a
+ end
+ assert_equal('10 was not recognized for preload', exception.message)
+ end
+
+
test "preloading readonly association" do
# has-one
firm = Firm.where(id: "1").preload(:readonly_account).first!
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index abbe37ab38..7718b29125 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -3,6 +3,7 @@ require 'models/developer'
require 'models/computer'
require 'models/project'
require 'models/company'
+require 'models/course'
require 'models/customer'
require 'models/order'
require 'models/categorization'
@@ -14,6 +15,7 @@ require 'models/tagging'
require 'models/parrot'
require 'models/person'
require 'models/pirate'
+require 'models/professor'
require 'models/treasure'
require 'models/price_estimate'
require 'models/club'
@@ -923,4 +925,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_deprecated { developer.projects(true) }
end
+
+ def test_alternate_database
+ professor = Professor.create(name: "Plum")
+ course = Course.create(name: "Forensics")
+ assert_equal 0, professor.courses.count
+ assert_nothing_raised do
+ professor.courses << course
+ end
+ assert_equal 1, professor.courses.count
+ 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 21e2ee3b11..f487065d9d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -31,6 +31,8 @@ require 'models/student'
require 'models/pirate'
require 'models/ship'
require 'models/ship_part'
+require 'models/treasure'
+require 'models/parrot'
require 'models/tyre'
require 'models/subscriber'
require 'models/subscription'
@@ -932,6 +934,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, new_firm.clients_of_firm.size
end
+ def test_has_many_without_counter_cache_option
+ # Ship has a conventionally named `treasures_count` column, but the counter_cache
+ # option is not given on the association.
+ ship = Ship.create(name: 'Countless', treasures_count: 10)
+
+ assert_not ship.treasures.instance_variable_get('@association').send(:has_cached_counter?)
+
+ # Count should come from sql count() of treasures rather than treasures_count attribute
+ assert_equal ship.treasures.size, 0
+
+ assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do
+ ship.treasures.create(name: 'Gold')
+ end
+
+ assert_no_difference lambda { ship.reload.treasures_count }, "treasures_count should not be changed" do
+ ship.treasures.destroy_all
+ end
+ end
+
def test_deleting_updates_counter_cache
topic = Topic.order("id ASC").first
assert_equal topic.replies.to_a.size, topic.replies_count
@@ -2308,4 +2329,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_instance_of PostWithErrorDestroying, error.record
end
+
+ def test_ids_reader_memoization
+ car = Car.create!(name: 'Tofaş')
+ bulb = Bulb.create!(car: car)
+
+ assert_equal [bulb.id], car.bulb_ids
+ assert_no_queries { car.bulb_ids }
+ 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 ce2557339e..b22ce42696 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1171,4 +1171,32 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_deprecated { post.people(true) }
end
+
+ def test_has_many_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes
+ member = Member.create!
+ club = Club.create!
+ TenantMembership.create!(
+ member: member,
+ club: club
+ )
+
+ TenantMembership.current_member = member
+
+ tenant_clubs = member.tenant_clubs
+ assert_equal [club], tenant_clubs
+
+ TenantMembership.current_member = nil
+
+ other_member = Member.create!
+ other_club = Club.create!
+ TenantMembership.create!(
+ member: other_member,
+ club: other_club
+ )
+
+ tenant_clubs = other_member.tenant_clubs
+ assert_equal [other_club], tenant_clubs
+ ensure
+ TenantMembership.current_member = nil
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 2c8feb9152..b2b46812b9 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -16,6 +16,10 @@ require 'models/owner'
require 'models/post'
require 'models/comment'
require 'models/categorization'
+require 'models/customer'
+require 'models/carrier'
+require 'models/shop_account'
+require 'models/customer_carrier'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
@@ -347,16 +351,33 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_association_force_reload_with_only_true_is_deprecated
- member = Member.find(1)
+ def test_has_one_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes
+ customer = Customer.create!
+ carrier = Carrier.create!
+ customer_carrier = CustomerCarrier.create!(
+ customer: customer,
+ carrier: carrier,
+ )
+ account = ShopAccount.create!(customer_carrier: customer_carrier)
- assert_deprecated { member.club(true) }
- end
+ CustomerCarrier.current_customer = customer
- def test_has_one_through_associations_are_mutable_unless_through_belongs_to
- member_detail = MemberDetail.new(member: @member)
- assert_raise(ActiveRecord::HasOneThroughCantAssociateThroughHasOneOrManyReflection) do
- member_detail.membership = Membership.new
- end
+ account_carrier = account.carrier
+ assert_equal carrier, account_carrier
+
+ CustomerCarrier.current_customer = nil
+
+ other_carrier = Carrier.create!
+ other_customer = Customer.create!
+ other_customer_carrier = CustomerCarrier.create!(
+ customer: other_customer,
+ carrier: other_carrier,
+ )
+ other_account = ShopAccount.create!(customer_carrier: other_customer_carrier)
+
+ account_carrier = other_account.carrier
+ assert_equal other_carrier, account_carrier
+ ensure
+ CustomerCarrier.current_customer = nil
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index bd74821d87..264b275181 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -152,7 +152,7 @@ module ActiveRecord
Type::String.new(limit: 50))
int_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
Type::Integer.new)
- refute_equal string_array, int_array
+ assert_not_equal string_array, int_array
assert_equal string_array, klass.type_for_attribute("my_array")
assert_equal int_array, klass.type_for_attribute("my_int_array")
end
@@ -167,7 +167,7 @@ module ActiveRecord
Type::String.new(limit: 50))
int_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
Type::Integer.new)
- refute_equal string_range, int_range
+ assert_not_equal string_range, int_range
assert_equal string_range, klass.type_for_attribute("my_range")
assert_equal int_range, klass.type_for_attribute("my_int_range")
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 0791dde1f2..48dc8135ab 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -53,7 +53,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_raise_if_select_is_set_without_id
- assert_raise(RuntimeError) do
+ assert_raise(ArgumentError) do
Post.select(:title).find_each(batch_size: 1) { |post|
flunk "should not call this block"
}
@@ -199,7 +199,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_return_an_enumerator
enum = nil
- assert_queries(0) do
+ assert_no_queries do
enum = Post.find_in_batches(:batch_size => 1)
end
assert_queries(4) do
@@ -210,6 +210,234 @@ class EachTest < ActiveRecord::TestCase
end
end
+ def test_in_batches_should_not_execute_any_query
+ assert_no_queries do
+ assert_kind_of ActiveRecord::Batches::BatchEnumerator, Post.in_batches(of: 2)
+ end
+ end
+
+ def test_in_batches_should_yield_relation_if_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_should_be_enumerable_if_no_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_yield_record_if_block_is_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record do |post|
+ assert post.title.present?
+ assert_kind_of Post, post
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_return_enumerator_if_no_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record.with_index do |post, i|
+ assert post.title.present?
+ assert_kind_of Post, post
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_be_ordered_by_id
+ ids = Post.order('id ASC').pluck(:id)
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record.with_index do |post, i|
+ assert_equal ids[i], post.id
+ end
+ end
+ end
+
+ def test_in_batches_update_all_affect_all_records
+ assert_queries(6 + 6) do # 6 selects, 6 updates
+ Post.in_batches(of: 2).update_all(title: "updated-title")
+ end
+ assert_equal Post.all.pluck(:title), ["updated-title"] * Post.count
+ end
+
+ def test_in_batches_delete_all_should_not_delete_records_in_other_batches
+ not_deleted_count = Post.where('id <= 2').count
+ Post.where('id > 2').in_batches(of: 2).delete_all
+ assert_equal 0, Post.where('id > 2').count
+ assert_equal not_deleted_count, Post.count
+ end
+
+ def test_in_batches_should_not_be_loaded
+ Post.in_batches(of: 1) do |relation|
+ assert_not relation.loaded?
+ end
+
+ Post.in_batches(of: 1, load: false) do |relation|
+ assert_not relation.loaded?
+ end
+ end
+
+ def test_in_batches_should_be_loaded
+ Post.in_batches(of: 1, load: true) do |relation|
+ assert relation.loaded?
+ end
+ end
+
+ def test_in_batches_if_not_loaded_executes_more_queries
+ assert_queries(@total + 1) do
+ Post.in_batches(of: 1, load: false) do |relation|
+ assert_not relation.loaded?
+ end
+ end
+ end
+
+ def test_in_batches_should_return_relations
+ assert_queries(@total + 1) do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_should_start_from_the_start_option
+ post = Post.order('id ASC').where('id >= ?', 2).first
+ assert_queries(2) do
+ relation = Post.in_batches(of: 1, begin_at: 2).first
+ assert_equal post, relation.first
+ end
+ end
+
+ def test_in_batches_should_end_at_the_end_option
+ post = Post.order('id DESC').where('id <= ?', 5).first
+ assert_queries(7) do
+ relation = Post.in_batches(of: 1, end_at: 5, load: true).reverse_each.first
+ assert_equal post, relation.last
+ end
+ end
+
+ def test_in_batches_shouldnt_execute_query_unless_needed
+ assert_queries(2) do
+ Post.in_batches(of: @total) { |relation| assert_kind_of ActiveRecord::Relation, relation }
+ end
+
+ assert_queries(1) do
+ Post.in_batches(of: @total + 1) { |relation| assert_kind_of ActiveRecord::Relation, relation }
+ end
+ end
+
+ def test_in_batches_should_quote_batch_order
+ c = Post.connection
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+ end
+ end
+ end
+
+ def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
+ not_a_post = "not a post"
+ def not_a_post.id
+ raise StandardError.new("not_a_post had #id called on it")
+ end
+
+ assert_nothing_raised do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+
+ relation = [not_a_post] * relation.count
+ end
+ end
+ end
+
+ def test_in_batches_should_not_ignore_default_scope_without_order_statements
+ special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort
+ posts = []
+ SpecialPostWithDefaultScope.in_batches do |relation|
+ posts.concat(relation)
+ end
+ assert_equal special_posts_ids, posts.map(&:id)
+ end
+
+ def test_in_batches_should_not_modify_passed_options
+ assert_nothing_raised do
+ Post.in_batches({ of: 42, begin_at: 1 }.freeze){}
+ end
+ end
+
+ def test_in_batches_should_use_any_column_as_primary_key
+ nick_order_subscribers = Subscriber.order('nick asc')
+ start_nick = nick_order_subscribers.second.nick
+
+ subscribers = []
+ Subscriber.in_batches(of: 1, begin_at: start_nick) do |relation|
+ subscribers.concat(relation)
+ end
+
+ assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id)
+ end
+
+ def test_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
+ assert_queries(Subscriber.count + 1) do
+ Subscriber.in_batches(of: 1, load: true) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Subscriber, relation.first
+ end
+ end
+ end
+
+ def test_in_batches_should_return_an_enumerator
+ enum = nil
+ assert_no_queries do
+ enum = Post.in_batches(of: 1)
+ end
+ assert_queries(4) do
+ enum.first(4) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+ end
+ end
+ end
+
+ def test_in_batches_relations_should_not_overlap_with_each_other
+ seen_posts = []
+ Post.in_batches(of: 2, load: true) do |relation|
+ relation.to_a.each do |post|
+ assert_not seen_posts.include?(post)
+ seen_posts << post
+ end
+ end
+ end
+
+ def test_in_batches_relations_with_condition_should_not_overlap_with_each_other
+ seen_posts = []
+ author_id = Post.first.author_id
+ posts_by_author = Post.where(author_id: author_id)
+ Post.in_batches(of: 2) do |batch|
+ seen_posts += batch.where(author_id: author_id)
+ end
+
+ assert_equal posts_by_author.pluck(:id).sort, seen_posts.map(&:id).sort
+ end
+
+ def test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches
+ Post.update_all(author_id: 0)
+ person = Post.last
+ person.update_attributes(author_id: 1)
+
+ Post.in_batches(of: 2) do |batch|
+ batch.where('author_id >= 1').update_all('author_id = author_id + 1')
+ end
+ assert_equal 2, person.reload.author_id # incremented only once
+ end
+
def test_find_in_batches_start_deprecated
assert_deprecated do
assert_queries(@total) do
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index ba23049a92..7c930de97b 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -40,43 +40,43 @@ class EnumTest < ActiveRecord::TestCase
published, written = Book.statuses[:published], Book.statuses[:written]
assert_equal @book, Book.where(status: published).first
- refute_equal @book, Book.where(status: written).first
+ assert_not_equal @book, Book.where(status: written).first
assert_equal @book, Book.where(status: [published]).first
- refute_equal @book, Book.where(status: [written]).first
- refute_equal @book, Book.where("status <> ?", published).first
+ assert_not_equal @book, Book.where(status: [written]).first
+ assert_not_equal @book, Book.where("status <> ?", published).first
assert_equal @book, Book.where("status <> ?", written).first
end
test "find via where with symbols" do
assert_equal @book, Book.where(status: :published).first
- refute_equal @book, Book.where(status: :written).first
+ assert_not_equal @book, Book.where(status: :written).first
assert_equal @book, Book.where(status: [:published]).first
- refute_equal @book, Book.where(status: [:written]).first
- refute_equal @book, Book.where.not(status: :published).first
+ assert_not_equal @book, Book.where(status: [:written]).first
+ assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
end
test "find via where with strings" do
assert_equal @book, Book.where(status: "published").first
- refute_equal @book, Book.where(status: "written").first
+ assert_not_equal @book, Book.where(status: "written").first
assert_equal @book, Book.where(status: ["published"]).first
- refute_equal @book, Book.where(status: ["written"]).first
- refute_equal @book, Book.where.not(status: "published").first
+ assert_not_equal @book, Book.where(status: ["written"]).first
+ assert_not_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
end
test "build from scope" do
assert Book.written.build.written?
- refute Book.written.build.proposed?
+ assert_not Book.written.build.proposed?
end
test "build from where" do
assert Book.where(status: Book.statuses[:written]).build.written?
- refute Book.where(status: Book.statuses[:written]).build.proposed?
+ assert_not Book.where(status: Book.statuses[:written]).build.proposed?
assert Book.where(status: :written).build.written?
- refute Book.where(status: :written).build.proposed?
+ assert_not Book.where(status: :written).build.proposed?
assert Book.where(status: "written").build.written?
- refute Book.where(status: "written").build.proposed?
+ assert_not Book.where(status: "written").build.proposed?
end
test "update by declaration" do
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 03a187ae92..76ea950fb1 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -217,6 +217,13 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ def test_yaml_file_with_invalid_column
+ e = assert_raise(ActiveRecord::Fixture::FixtureError) do
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
+ end
+ assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+ end
+
def test_omap_fixtures
assert_nothing_raised do
fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered")
diff --git a/activerecord/test/cases/migration/postgresql_geometric_types_test.rb b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb
new file mode 100644
index 0000000000..e4772905bb
--- /dev/null
+++ b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class PostgreSQLGeometricTypesTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_creating_column_with_point_type
+ connection.create_table(table_name) do |t|
+ t.point :foo_point
+ end
+
+ assert_column_exists(:foo_point)
+ assert_type_correct(:foo_point, :point)
+ end
+
+ def test_creating_column_with_line_type
+ connection.create_table(table_name) do |t|
+ t.line :foo_line
+ end
+
+ assert_column_exists(:foo_line)
+ assert_type_correct(:foo_line, :line)
+ end
+
+ def test_creating_column_with_lseg_type
+ connection.create_table(table_name) do |t|
+ t.lseg :foo_lseg
+ end
+
+ assert_column_exists(:foo_lseg)
+ assert_type_correct(:foo_lseg, :lseg)
+ end
+
+ def test_creating_column_with_box_type
+ connection.create_table(table_name) do |t|
+ t.box :foo_box
+ end
+
+ assert_column_exists(:foo_box)
+ assert_type_correct(:foo_box, :box)
+ end
+
+ def test_creating_column_with_path_type
+ connection.create_table(table_name) do |t|
+ t.path :foo_path
+ end
+
+ assert_column_exists(:foo_path)
+ assert_type_correct(:foo_path, :path)
+ end
+
+ def test_creating_column_with_polygon_type
+ connection.create_table(table_name) do |t|
+ t.polygon :foo_polygon
+ end
+
+ assert_column_exists(:foo_polygon)
+ assert_type_correct(:foo_polygon, :polygon)
+ end
+
+ def test_creating_column_with_circle_type
+ connection.create_table(table_name) do |t|
+ t.circle :foo_circle
+ end
+
+ assert_column_exists(:foo_circle)
+ assert_type_correct(:foo_circle, :circle)
+ end
+ end
+
+ private
+ def assert_column_exists(column_name)
+ columns = connection.columns(table_name)
+ assert columns.map(&:name).include?(column_name.to_s)
+ end
+
+ def assert_type_correct(column_name, type)
+ columns = connection.columns(table_name)
+ column = columns.select{ |c| c.name == column_name.to_s }.first
+ assert_equal type.to_s, column.sql_type
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 0dbc60940e..86316ab476 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/comment'
require 'models/developer'
require 'models/computer'
+require 'models/vehicle'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -453,4 +454,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, scope.where_clause.ast.children.length
assert_equal Developer.where(name: "David"), scope
end
+
+ def test_with_abstract_class_where_clause_should_not_be_duplicated
+ scope = Bus.all
+ assert_equal scope.where_clause.ast.children.length, 1
+ end
end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 35b13ea247..14b80f4df4 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -8,7 +8,7 @@ require 'models/post'
class SerializationTest < ActiveRecord::TestCase
fixtures :books
- FORMATS = [ :xml, :json ]
+ FORMATS = [ :json ]
def setup
@contact_attributes = {
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 084302cde5..184ff7fc63 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -204,7 +204,7 @@ module ActiveRecord
end
def test_structure_dump
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} my-app-db").returns(true)
+ Kernel.expects(:system).with('pg_dump', '-i', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
@@ -212,7 +212,7 @@ module ActiveRecord
def test_structure_dump_with_schema_search_path
@configuration['schema_search_path'] = 'foo,bar'
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true)
+ Kernel.expects(:system).with('pg_dump', '-i', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', 'my-app-db').returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
@@ -220,7 +220,7 @@ module ActiveRecord
def test_structure_dump_with_schema_search_path_and_dump_schemas_all
@configuration['schema_search_path'] = 'foo,bar'
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} my-app-db").returns(true)
+ Kernel.expects(:system).with("pg_dump", '-i', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true)
with_dump_schemas(:all) do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
@@ -228,7 +228,7 @@ module ActiveRecord
end
def test_structure_dump_with_dump_schemas_string
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true)
+ Kernel.expects(:system).with("pg_dump", '-i', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', "my-app-db").returns(true)
with_dump_schemas('foo,bar') do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
@@ -261,14 +261,14 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql -X -q -f #{filename} my-app-db")
+ Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with("psql -X -q -f awesome\\ file.sql my-app-db")
+ Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 49ada22529..b3c42c8e42 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -11,7 +11,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_laster_raise_if_non_persisted
invoice = Invoice.new
Invoice.transaction do
- refute invoice.persisted?
+ assert_not invoice.persisted?
assert_raises(ActiveRecord::ActiveRecordError) do
invoice.touch_later
end
@@ -21,7 +21,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_later_dont_set_dirty_attributes
invoice = Invoice.create!
invoice.touch_later
- refute invoice.changed?
+ assert_not invoice.changed?
end
def test_touch_later_update_the_attributes
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index ceca2c8366..7502a55391 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -4,6 +4,7 @@ require 'models/reply'
require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
+require 'models/dashboard'
class Wizard < ActiveRecord::Base
self.abstract_class = true
@@ -446,4 +447,26 @@ class UniquenessValidationTest < ActiveRecord::TestCase
key2.key_number = 10
assert_not key2.valid?
end
+
+ def test_validate_uniqueness_without_primary_key
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "dashboards"
+
+ validates_uniqueness_of :dashboard_id
+
+ def self.name; "Dashboard" end
+ end
+
+ abc = klass.create!(dashboard_id: "abc")
+ assert klass.new(dashboard_id: "xyz").valid?
+ assert_not klass.new(dashboard_id: "abc").valid?
+
+ abc.dashboard_id = "def"
+
+ e = assert_raises ActiveRecord::UnknownPrimaryKey do
+ abc.save!
+ end
+ assert_match(/\AUnknown primary key for table dashboards in model/, e.message)
+ assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message)
+ end
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
deleted file mode 100644
index b30b50f597..0000000000
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ /dev/null
@@ -1,447 +0,0 @@
-require "cases/helper"
-require "rexml/document"
-require 'models/contact'
-require 'models/post'
-require 'models/author'
-require 'models/comment'
-require 'models/company_in_module'
-require 'models/toy'
-require 'models/topic'
-require 'models/reply'
-require 'models/company'
-
-class XmlSerializationTest < ActiveRecord::TestCase
- def test_should_serialize_default_root
- @xml = Contact.new.to_xml
- assert_match %r{^<contact>}, @xml
- assert_match %r{</contact>$}, @xml
- end
-
- def test_should_serialize_default_root_with_namespace
- @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml
- assert_match %r{</contact>$}, @xml
- end
-
- def test_should_serialize_custom_root
- @xml = Contact.new.to_xml :root => 'xml_contact'
- assert_match %r{^<xml-contact>}, @xml
- assert_match %r{</xml-contact>$}, @xml
- end
-
- def test_should_allow_undasherized_tags
- @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false
- assert_match %r{^<xml_contact>}, @xml
- assert_match %r{</xml_contact>$}, @xml
- assert_match %r{<created_at}, @xml
- end
-
- def test_should_allow_camelized_tags
- @xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true
- assert_match %r{^<XmlContact>}, @xml
- assert_match %r{</XmlContact>$}, @xml
- assert_match %r{<CreatedAt}, @xml
- end
-
- def test_should_allow_skipped_types
- @xml = Contact.new(:age => 25).to_xml :skip_types => true
- assert %r{<age>25</age>}.match(@xml)
- end
-
- def test_should_include_yielded_additions
- @xml = Contact.new.to_xml do |xml|
- xml.creator "David"
- end
- assert_match %r{<creator>David</creator>}, @xml
- end
-
- def test_to_xml_with_block
- value = "Rockin' the block"
- xml = Contact.new.to_xml(:skip_instruct => true) do |_xml|
- _xml.tag! "arbitrary-element", value
- end
- assert_equal "<contact>", xml.first(9)
- assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
- end
-
- def test_should_skip_instruct_for_included_records
- @contact = Contact.new
- @contact.alternative = Contact.new(:name => 'Copa Cabana')
- @xml = @contact.to_xml(:include => [ :alternative ])
- assert_equal @xml.index('<?xml '), 0
- assert_nil @xml.index('<?xml ', 1)
- end
-end
-
-class DefaultXmlSerializationTest < ActiveRecord::TestCase
- def setup
- @contact = Contact.new(
- :name => 'aaron stack',
- :age => 25,
- :avatar => 'binarydata',
- :created_at => Time.utc(2006, 8, 1),
- :awesome => false,
- :preferences => { :gem => 'ruby' }
- )
- end
-
- def test_should_serialize_string
- assert_match %r{<name>aaron stack</name>}, @contact.to_xml
- end
-
- def test_should_serialize_integer
- assert_match %r{<age type="integer">25</age>}, @contact.to_xml
- end
-
- def test_should_serialize_binary
- xml = @contact.to_xml
- assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, xml
- assert_match %r{<avatar(.*)(type="binary")}, xml
- assert_match %r{<avatar(.*)(encoding="base64")}, xml
- end
-
- def test_should_serialize_datetime
- assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
- end
-
- def test_should_serialize_boolean
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml
- end
-
- def test_should_serialize_hash
- assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml
- end
-
- def test_uses_serializable_hash_with_only_option
- def @contact.serializable_hash(options=nil)
- super(only: %w(name))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{age}, xml
- assert_no_match %r{awesome}, xml
- end
-
- def test_uses_serializable_hash_with_except_option
- def @contact.serializable_hash(options=nil)
- super(except: %w(age))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml
- assert_no_match %r{age}, xml
- end
-
- def test_does_not_include_inheritance_column_from_sti
- @contact = ContactSti.new(@contact.attributes)
- assert_equal 'ContactSti', @contact.type
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{<type}, xml
- assert_no_match %r{ContactSti}, xml
- end
-
- def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
- @contact = ContactSti.new(@contact.attributes)
- assert_equal 'ContactSti', @contact.type
-
- def @contact.serializable_hash(options={})
- super({ except: %w(age) }.merge!(options))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{age}, xml
- assert_no_match %r{<type}, xml
- assert_no_match %r{ContactSti}, xml
- end
-end
-
-class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
- def test_should_serialize_datetime_with_timezone
- with_timezone_config zone: "Pacific Time (US & Canada)" do
- toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1))
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- end
- end
-
- def test_should_serialize_datetime_with_timezone_reloaded
- with_timezone_config zone: "Pacific Time (US & Canada)" do
- toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- end
- end
-end
-
-class NilXmlSerializationTest < ActiveRecord::TestCase
- def setup
- @xml = Contact.new.to_xml(:root => 'xml_contact')
- end
-
- def test_should_serialize_string
- assert_match %r{<name nil="true"/>}, @xml
- end
-
- def test_should_serialize_integer
- assert %r{<age (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{nil="true"}, attributes
- assert_match %r{type="integer"}, attributes
- end
-
- def test_should_serialize_binary
- assert %r{<avatar (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{type="binary"}, attributes
- assert_match %r{encoding="base64"}, attributes
- assert_match %r{nil="true"}, attributes
- end
-
- def test_should_serialize_datetime
- assert %r{<created-at (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{nil="true"}, attributes
- assert_match %r{type="dateTime"}, attributes
- end
-
- def test_should_serialize_boolean
- assert %r{<awesome (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{type="boolean"}, attributes
- assert_match %r{nil="true"}, attributes
- end
-
- def test_should_serialize_yaml
- assert_match %r{<preferences nil=\"true\"/>}, @xml
- end
-end
-
-class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :accounts, :authors, :posts, :projects
-
- def test_to_xml
- xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
- bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
- written_on_in_current_timezone = topics(:first).written_on.xmlschema
-
- assert_equal "topic", xml.root.name
- assert_equal "The First Topic" , xml.elements["//title"].text
- assert_equal "David" , xml.elements["//author-name"].text
- assert_match "Have a nice day", xml.elements["//content"].text
-
- assert_equal "1", xml.elements["//id"].text
- assert_equal "integer" , xml.elements["//id"].attributes['type']
-
- assert_equal "1", xml.elements["//replies-count"].text
- assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
-
- assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "dateTime" , xml.elements["//written-on"].attributes['type']
-
- assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
-
- assert_equal nil, xml.elements["//parent-id"].text
- assert_equal "integer", xml.elements["//parent-id"].attributes['type']
- assert_equal "true", xml.elements["//parent-id"].attributes['nil']
-
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_equal "2004-04-15", xml.elements["//last-read"].text
- assert_equal "date" , xml.elements["//last-read"].attributes['type']
-
- # Oracle and DB2 don't have true boolean or time-only fields
- unless current_adapter?(:OracleAdapter, :DB2Adapter)
- assert_equal "false", xml.elements["//approved"].text
- assert_equal "boolean" , xml.elements["//approved"].attributes['type']
-
- assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type']
- end
- end
-
- def test_except_option
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
- assert_equal "<topic>", xml.first(7)
- assert !xml.include?(%(<title>The First Topic</title>))
- assert xml.include?(%(<author-name>David</author-name>))
-
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
- assert !xml.include?(%(<title>The First Topic</title>))
- assert !xml.include?(%(<author-name>David</author-name>))
- end
-
- # to_xml used to mess with the hash the user provided which
- # caused the builder to be reused. This meant the document kept
- # getting appended to.
-
- def test_modules
- projects = MyApplication::Business::Project.all
- xml = projects.to_xml
- root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize
- assert_match "<#{root} type=\"array\">", xml
- assert_match "</#{root}>", xml
- end
-
- def test_passing_hash_shouldnt_reuse_builder
- options = {:include=>:posts}
- david = authors(:david)
- first_xml_size = david.to_xml(options).size
- second_xml_size = david.to_xml(options).size
- assert_equal first_xml_size, second_xml_size
- end
-
- def test_include_uses_association_name
- xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0
- assert_match %r{<hello-posts type="array">}, xml
- assert_match %r{<hello-post type="Post">}, xml
- assert_match %r{<hello-post type="StiPost">}, xml
- end
-
- def test_included_associations_should_skip_types
- xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true
- assert_match %r{<hello-posts>}, xml
- assert_match %r{<hello-post>}, xml
- assert_match %r{<hello-post>}, xml
- end
-
- def test_including_has_many_association
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
- assert_equal "<topic>", xml.first(7)
- assert xml.include?(%(<replies type="array"><reply>))
- assert xml.include?(%(<title>The Second Topic of the day</title>))
- end
-
- def test_including_belongs_to_association
- xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert !xml.include?("<firm>")
-
- xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?("<firm>")
- end
-
- def test_including_multiple_associations
- xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<account>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_including_association_with_options
- xml = companies(:first_firm).to_xml(
- :indent => 0, :skip_instruct => true,
- :include => { :clients => { :only => :name } }
- )
-
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<client><name>Summit</name></client>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_methods_are_called_on_object
- xml = authors(:david).to_xml :methods => :label, :indent => 0
- assert_match %r{<label>.*</label>}, xml
- end
-
- def test_should_not_call_methods_on_associations_that_dont_respond
- xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2
- assert !authors(:david).hello_posts.first.respond_to?(:label)
- assert_match %r{^ <label>.*</label>}, xml
- assert_no_match %r{^ <label>}, xml
- end
-
- def test_procs_are_called_on_object
- proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
- xml = authors(:david).to_xml(:procs => [ proc ])
- assert_match %r{<nationality>Danish</nationality>}, xml
- end
-
- def test_dual_arity_procs_are_called_on_object
- proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- xml = authors(:david).to_xml(:procs => [ proc ])
- assert_match %r{<name-reverse>divaD</name-reverse>}, xml
- end
-
- def test_top_level_procs_arent_applied_to_associations
- author_proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
- xml = authors(:david).to_xml(:procs => [ author_proc ], :include => :posts, :indent => 2)
-
- assert_match %r{^ <nationality>Danish</nationality>}, xml
- assert_no_match %r{^ {6}<nationality>Danish</nationality>}, xml
- end
-
- def test_procs_on_included_associations_are_called
- posts_proc = Proc.new { |options| options[:builder].tag!('copyright', 'DHH') }
- xml = authors(:david).to_xml(
- :indent => 2,
- :include => {
- :posts => { :procs => [ posts_proc ] }
- }
- )
-
- assert_no_match %r{^ <copyright>DHH</copyright>}, xml
- assert_match %r{^ {6}<copyright>DHH</copyright>}, xml
- end
-
- def test_should_include_empty_has_many_as_empty_array
- authors(:david).posts.delete_all
- xml = authors(:david).to_xml :include=>:posts, :indent => 2
-
- assert_equal [], Hash.from_xml(xml)['author']['posts']
- assert_match %r{^ <posts type="array"/>}, xml
- end
-
- def test_should_has_many_array_elements_should_include_type_when_different_from_guessed_value
- xml = authors(:david).to_xml :include=>:posts_with_comments, :indent => 2
-
- assert Hash.from_xml(xml)
- assert_match %r{^ <posts-with-comments type="array">}, xml
- assert_match %r{^ <posts-with-comment type="Post">}, xml
- assert_match %r{^ <posts-with-comment type="StiPost">}, xml
-
- types = Hash.from_xml(xml)['author']['posts_with_comments'].collect {|t| t['type'] }
- assert types.include?('SpecialPost')
- assert types.include?('Post')
- assert types.include?('StiPost')
- end
-
- def test_should_produce_xml_for_methods_returning_array
- xml = authors(:david).to_xml(:methods => :social)
- array = Hash.from_xml(xml)['author']['social']
- assert_equal 2, array.size
- assert array.include? 'twitter'
- assert array.include? 'github'
- end
-
- def test_should_support_aliased_attributes
- xml = Author.select("name as firstname").to_xml
- Author.all.each do |author|
- assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml
- end
- end
-
- def test_array_to_xml_including_has_many_association
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
- assert xml.include?(%(<replies type="array"><reply>))
- end
-
- def test_array_to_xml_including_methods
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
- assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
- assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
- end
-
- def test_array_to_xml_including_has_one_association
- xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
- assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_array_to_xml_including_belongs_to_association
- xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- end
-end
diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml
new file mode 100644
index 0000000000..3e10331105
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/parrots.yml
@@ -0,0 +1,2 @@
+george:
+ arrr: "Curious George"
diff --git a/activerecord/test/models/carrier.rb b/activerecord/test/models/carrier.rb
new file mode 100644
index 0000000000..230be118c3
--- /dev/null
+++ b/activerecord/test/models/carrier.rb
@@ -0,0 +1,2 @@
+class Carrier < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index 6588531de6..4cd67c970a 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -1,6 +1,6 @@
class Categorization < ActiveRecord::Base
belongs_to :post
- belongs_to :category
+ belongs_to :category, counter_cache: true
belongs_to :named_category, :class_name => 'Category', :foreign_key => :named_category_name, :primary_key => :name
belongs_to :author
diff --git a/activerecord/test/models/customer_carrier.rb b/activerecord/test/models/customer_carrier.rb
new file mode 100644
index 0000000000..37186903ff
--- /dev/null
+++ b/activerecord/test/models/customer_carrier.rb
@@ -0,0 +1,14 @@
+class CustomerCarrier < ActiveRecord::Base
+ cattr_accessor :current_customer
+
+ belongs_to :customer
+ belongs_to :carrier
+
+ default_scope -> {
+ if current_customer
+ where(customer: current_customer)
+ else
+ all
+ end
+ }
+end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index dc0566d8a7..7693c6e515 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -26,6 +26,9 @@ class Member < ActiveRecord::Base
has_many :current_memberships, -> { where :favourite => true }
has_many :clubs, :through => :current_memberships
+ has_many :tenant_memberships
+ has_many :tenant_clubs, through: :tenant_memberships, class_name: 'Club', source: :club
+
has_one :club_through_many, :through => :current_memberships, :source => :club
belongs_to :admittable, polymorphic: true
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index df7167ee93..e181ba1f11 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -18,3 +18,18 @@ class SelectedMembership < Membership
select("'1' as foo")
end
end
+
+class TenantMembership < Membership
+ cattr_accessor :current_member
+
+ belongs_to :member
+ belongs_to :club
+
+ default_scope -> {
+ if current_member
+ where(member: current_member)
+ else
+ all
+ end
+ }
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 10f13b67da..81a18188d4 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -98,11 +98,11 @@ class Post < ActiveRecord::Base
end
end
- has_many :taggings_with_delete_all, :class_name => 'Tagging', :as => :taggable, :dependent => :delete_all
- has_many :taggings_with_destroy, :class_name => 'Tagging', :as => :taggable, :dependent => :destroy
+ has_many :taggings_with_delete_all, :class_name => 'Tagging', :as => :taggable, :dependent => :delete_all, counter_cache: :taggings_with_delete_all_count
+ has_many :taggings_with_destroy, :class_name => 'Tagging', :as => :taggable, :dependent => :destroy, counter_cache: :taggings_with_destroy_count
- has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy
- has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify
+ has_many :tags_with_destroy, :through => :taggings, :source => :tag, :dependent => :destroy, counter_cache: :tags_with_destroy_count
+ has_many :tags_with_nullify, :through => :taggings, :source => :tag, :dependent => :nullify, counter_cache: :tags_with_nullify_count
has_many :misc_tags, -> { where :tags => { :name => 'Misc' } }, :through => :taggings, :source => :tag
has_many :funky_tags, :through => :taggings, :source => :tag
diff --git a/activerecord/test/models/professor.rb b/activerecord/test/models/professor.rb
new file mode 100644
index 0000000000..7654eda0ef
--- /dev/null
+++ b/activerecord/test/models/professor.rb
@@ -0,0 +1,5 @@
+require_dependency 'models/arunit2_model'
+
+class Professor < ARUnit2Model
+ has_and_belongs_to_many :courses
+end
diff --git a/activerecord/test/models/shop_account.rb b/activerecord/test/models/shop_account.rb
new file mode 100644
index 0000000000..1580e8b20c
--- /dev/null
+++ b/activerecord/test/models/shop_account.rb
@@ -0,0 +1,6 @@
+class ShopAccount < ActiveRecord::Base
+ belongs_to :customer
+ belongs_to :customer_carrier
+
+ has_one :carrier, through: :customer_carrier
+end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index ffc65466d5..63ff0c23ec 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -1,6 +1,7 @@
class Treasure < ActiveRecord::Base
has_and_belongs_to_many :parrots
belongs_to :looter, :polymorphic => true
+ # No counter_cache option given
belongs_to :ship
has_many :price_estimates, :as => :estimate_of
diff --git a/activerecord/test/models/vehicle.rb b/activerecord/test/models/vehicle.rb
new file mode 100644
index 0000000000..ef26170f1f
--- /dev/null
+++ b/activerecord/test/models/vehicle.rb
@@ -0,0 +1,7 @@
+class Vehicle < ActiveRecord::Base
+ self.abstract_class = true
+ default_scope -> { where("tires_count IS NOT NULL") }
+end
+
+class Bus < Vehicle
+end \ No newline at end of file
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 7bab675b2a..6f34115534 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -131,6 +131,8 @@ ActiveRecord::Schema.define do
t.timestamps null: false
end
+ create_table :carriers, force: true
+
create_table :categories, force: true do |t|
t.string :name, null: false
t.string :type
@@ -237,6 +239,11 @@ ActiveRecord::Schema.define do
t.string :gps_location
end
+ create_table :customer_carriers, force: true do |t|
+ t.references :customer
+ t.references :carrier
+ end
+
create_table :dashboards, force: true, id: false do |t|
t.string :dashboard_id
t.string :name
@@ -667,6 +674,8 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :pirate_id
t.integer :update_only_pirate_id
+ # Conventionally named column for counter_cache
+ t.integer :treasures_count, default: 0
t.datetime :created_at
t.datetime :created_on
t.datetime :updated_at
@@ -683,6 +692,11 @@ ActiveRecord::Schema.define do
t.belongs_to :ship
end
+ create_table :shop_accounts, force: true do |t|
+ t.references :customer
+ t.references :customer_carrier
+ end
+
create_table :speedometers, force: true, id: false do |t|
t.string :speedometer_id
t.string :name
@@ -939,3 +953,12 @@ end
College.connection.create_table :colleges, force: true do |t|
t.column :name, :string, null: false
end
+
+Professor.connection.create_table :professors, force: true do |t|
+ t.column :name, :string, null: false
+end
+
+Professor.connection.create_table :courses_professors, id: false, force: true do |t|
+ t.references :course
+ t.references :professor
+end
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index d11fd9cfc1..c5334e8596 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,6 +1,7 @@
require 'active_support/logger'
require 'models/college'
require 'models/course'
+require 'models/professor'
module ARTest
def self.connection_name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 7148f289bb..eaaa3df061 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Deprecate `:prefix` option of `number_to_human_size` with no replacement.
+
+ *Jean Boussier*
+
* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
twz = DateTime.now.in_time_zone
twz.eql?(twz.dup) => true
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index c18c1c87c9..6d2ae9be76 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -26,4 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency 'minitest', '~> 5.1'
s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
s.add_dependency 'concurrent-ruby', '~> 0.9.0'
+ s.add_dependency 'method_source'
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 837974bc85..8253a76383 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -26,7 +26,7 @@ module ActiveSupport
end
class << self
- # Creates a new CacheStore object according to the given options.
+ # Creates a new Store object according to the given options.
#
# If no arguments are passed to this method, then a new
# ActiveSupport::Cache::MemoryStore object will be returned.
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index f35e1f5098..fefba5b0fd 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -741,10 +741,10 @@ module ActiveSupport
# callback chain, preventing following before and around callbacks from
# being called and the event from being triggered.
# This should be a lambda to be executed.
- # The current object and the return result of the callback will be called
- # with the lambda.
+ # The current object and the result lambda of the callback will be provided
+ # to the terminator lambda.
#
- # define_callbacks :validate, terminator: ->(target, result) { result == false }
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index fad6fa8d9d..ab635f6db8 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -8,43 +8,45 @@ class File
# file.write('hello')
# end
#
- # If your temp directory is not on the same filesystem as the file you're
- # trying to write, you can provide a different temporary directory.
+ # This method needs to create a temporary file. By default it will create it
+ # in the same directory as the destination file. If you don't like this
+ # behaviour you can provide a different directory but it must be on the
+ # same physical filesystem as the the file you're trying to write.
#
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
# file.write('hello')
# end
- def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
+ def self.atomic_write(file_name, temp_dir = dirname(file_name))
require 'tempfile' unless defined?(Tempfile)
- require 'fileutils' unless defined?(FileUtils)
- temp_file = Tempfile.new(basename(file_name), temp_dir)
- temp_file.binmode
- return_val = yield temp_file
- temp_file.close
+ Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
+ temp_file.binmode
+ return_val = yield temp_file
+ temp_file.close
- if File.exist?(file_name)
- # Get original file permissions
- old_stat = stat(file_name)
- else
- # If not possible, probe which are the default permissions in the
- # destination directory.
- old_stat = probe_stat_in(dirname(file_name))
- end
-
- # Overwrite original file with temp file
- FileUtils.mv(temp_file.path, file_name)
+ old_stat = if exist?(file_name)
+ # Get original file permissions
+ stat(file_name)
+ elsif temp_dir != dirname(file_name)
+ # If not possible, probe which are the default permissions in the
+ # destination directory.
+ probe_stat_in(dirname(file_name))
+ end
- # Set correct permissions on new file
- begin
- chown(old_stat.uid, old_stat.gid, file_name)
- # This operation will affect filesystem ACL's
- chmod(old_stat.mode, file_name)
+ if old_stat
+ # Set correct permissions on new file
+ begin
+ chown(old_stat.uid, old_stat.gid, temp_file.path)
+ # This operation will affect filesystem ACL's
+ chmod(old_stat.mode, temp_file.path)
+ rescue Errno::EPERM, Errno::EACCES
+ # Changing file ownership failed, moving on.
+ end
+ end
- # Make sure we return the result of the yielded block
+ # Overwrite original file with temp file
+ rename(temp_file.path, file_name)
return_val
- rescue Errno::EPERM, Errno::EACCES
- # Changing file ownership failed, moving on.
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6e397abf51..2f6d38c1f6 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,8 +1,9 @@
class Hash
- # Returns a hash that includes everything but the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # Returns a hash that includes everything except given keys.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except(:c) # => { a: true, b: false }
+ # hash.except(:a, :b) # => { c: nil }
+ # hash # => { a: true, b: false, c: nil }
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
@@ -10,10 +11,10 @@ class Hash
dup.except!(*keys)
end
- # Replaces the hash without the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except!(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false }
+ # Removes the given keys from hash and returns it.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except!(:c) # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index d4e6b5a1ac..a77da573fe 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -206,7 +206,7 @@ class Module
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
- mattr_writer(*syms, &blk)
+ mattr_writer(*syms)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 69be6c4abc..c67eb25b68 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -8,7 +8,7 @@ module ActiveSupport
def try!(*a, &b)
if a.empty? && block_given?
- if b.arity.zero?
+ if b.arity == 0
instance_eval(&b)
else
yield self
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 0d5e02cd77..b2e713077c 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -172,7 +172,7 @@ class String
# uses the +pluralize+ method on the last word in the string.
#
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'ham_and_egg'.tableize # => "ham_and_eggs"
# 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
@@ -182,7 +182,7 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'ham_and_eggs'.classify # => "HamAndEgg"
# 'posts'.classify # => "Post"
def classify
ActiveSupport::Inflector.classify(self)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index c554501893..96156deebb 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -108,6 +108,12 @@ class Time
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
# <tt>:seconds</tt>.
+ #
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index c5d35b84f0..0371f760b0 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -92,7 +92,7 @@ module ActiveSupport
# hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = 'value'
#
- # This value can be later fetched using either +:key+ or +'key'+.
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
def []=(key, value)
regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 95f3f6255a..6775eec34b 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -37,10 +37,12 @@ module I18n
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
I18n.enforce_available_locales = false
+ reloadable_paths = []
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
+ reloadable_paths = value
+ app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
when :load_path
I18n.load_path += value
else
@@ -53,7 +55,14 @@ module I18n
# Restore available locales check so it will take place from now on.
I18n.enforce_available_locales = enforce_available_locales
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ directories = watched_dirs_with_extensions(reloadable_paths)
+ reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do
+ I18n.load_path.keep_if { |p| File.exist?(p) }
+ I18n.load_path |= reloadable_paths.map(&:existent).flatten
+
+ I18n.reload!
+ end
+
app.reloaders << reloader
ActionDispatch::Reloader.to_prepare do
reloader.execute_if_updated
@@ -96,5 +105,11 @@ module I18n
raise "Unexpected fallback type #{fallbacks.inspect}"
end
end
+
+ def self.watched_dirs_with_extensions(paths)
+ paths.each_with_object({}) do |path, result|
+ result[path.absolute_current] = path.extensions
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 8ac1820776..42560f3515 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -49,7 +49,7 @@ module ActiveSupport
end
def uncountable?(str)
- @regex_array.detect {|regex| regex.match(str) }
+ @regex_array.any? { |regex| regex === str }
end
private
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 899ba70af6..ab5372ac43 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -160,7 +160,7 @@ module ActiveSupport
# This method uses the #pluralize method on the last word in the string.
#
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
- # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('ham_and_egg') # => "ham_and_eggs"
# tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
@@ -170,7 +170,7 @@ module ActiveSupport
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with #constantize).
#
- # classify('egg_and_hams') # => "EggAndHam"
+ # classify('ham_and_eggs') # => "HamAndEgg"
# classify('posts') # => "Post"
#
# Singular names are not handled correctly:
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 4de7fa4e4b..b6c991a287 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -338,7 +338,7 @@ module ActiveSupport
end
# Redefine the === method so we can write shorter rules for grapheme cluster breaks
- @boundary.each do |k,_|
+ @boundary.each_key do |k|
@boundary[k].instance_eval do
def ===(other)
detect { |i| i === other } ? true : false
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 6bf8c7d5de..0131fe2572 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -111,7 +111,7 @@ module ActiveSupport
end
end
- class Timed < Evented
+ class Timed < Evented # :nodoc:
def publish(name, *args)
@delegate.call name, *args
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 258d9b34e1..38a9ce361d 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -220,8 +220,6 @@ module ActiveSupport
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
#
# ==== Examples
#
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index ac0d20b454..a4a8690bcd 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -7,6 +7,10 @@ module ActiveSupport
self.validate_float = true
def convert
+ if opts.key?(:prefix)
+ ActiveSupport::Deprecation.warn('The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.')
+ end
+
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
index e3cbe40308..fccaa54f40 100644
--- a/activesupport/lib/active_support/testing/method_call_assertions.rb
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -1,11 +1,13 @@
+require 'minitest/mock'
+
module ActiveSupport
module Testing
module MethodCallAssertions # :nodoc:
private
- def assert_called(object, method_name, message = nil, times: 1)
+ def assert_called(object, method_name, message = nil, times: 1, returns: nil)
times_called = 0
- object.stub(method_name, -> { times_called += 1 }) { yield }
+ object.stub(method_name, proc { times_called += 1; returns }) { yield }
error = "Expected #{method_name} to be called #{times} times, " \
"but was called #{times_called} times"
@@ -30,6 +32,10 @@ module ActiveSupport
def assert_not_called(object, method_name, message = nil, &block)
assert_called(object, method_name, message, times: 0, &block)
end
+
+ def stub_any_instance(klass, instance: klass.new)
+ klass.stub(:new, instance) { yield instance }
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index 48f3cc579f..128c5e3d1a 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -76,4 +76,10 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal 'default_reader_value', @module.defr
assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
+
+ def test_should_not_invoke_default_value_block_multiple_times
+ count = 0
+ @module.cattr_accessor(:defcount){ count += 1 }
+ assert_equal 1, count
+ end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 2d8796179e..0ff8f0f89b 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -280,14 +280,16 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__human_size_with_si_prefix
- assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
- assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
- assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
- assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ assert_deprecated do
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
+ assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ end
end
def test_to_s__human_size_with_options_hash
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 83efbffdfb..cb5230c4eb 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -234,15 +234,17 @@ module ActiveSupport
end
def test_number_to_human_size_with_si_prefix
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
- assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
- assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
- assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
- assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
- assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
- assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
- assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ assert_deprecated do
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ end
end
end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
index a9908aea0d..3e5ba7c079 100644
--- a/activesupport/test/testing/method_call_assertions_test.rb
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -27,6 +27,18 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
end
end
+ def test_assert_called_method_with_arguments
+ assert_called(@object, :<<) do
+ @object << 2
+ end
+ end
+
+ def test_assert_called_returns
+ assert_called(@object, :increment, returns: 10) do
+ assert_equal 10, @object.increment
+ end
+ end
+
def test_assert_called_failure
error = assert_raises(Minitest::Assertion) do
assert_called(@object, :increment) do
@@ -95,4 +107,17 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
assert_equal "Expected increment to be called 0 times, but was called 1 times.\nExpected: 0\n Actual: 1", error.message
end
+
+ def test_stub_any_instance
+ stub_any_instance(Level) do |instance|
+ assert_equal instance, Level.new
+ end
+ end
+
+ def test_stub_any_instance_with_instance
+ stub_any_instance(Level, instance: @object) do |instance|
+ assert_equal @object, instance
+ assert_equal instance, Level.new
+ end
+ end
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index fd177b4238..09fb7b1a0e 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Add code of conduct to contributing guide
+
+ *Jon Moss*
+
* New section in Configuring: Configuring Active Job
*Eliot Sykes*
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 1a4b736348..31a3f6c650 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -9,6 +9,7 @@ gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
+ gem 'rack', github: 'rack/rack'
end
require 'action_controller/railtie'
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 270dbe7df7..6ae50b2460 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -9,6 +9,7 @@ gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
+ gem 'rack', github: 'rack/rack'
gem 'sqlite3'
end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index b6b4562751..f6dce64280 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -9,6 +9,7 @@ gemfile(true) do
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
+ gem 'rack', github: 'rack/rack'
end
require 'active_support'
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index c31b50fcfc..c39cd34e9a 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -326,7 +326,7 @@ key. The list of emails can be an array of email addresses or a single string
with the addresses separated by commas.
```ruby
-class AdminMailer < ActionMailer::Base
+class AdminMailer < ApplicationMailer
default to: Proc.new { Admin.pluck(:email) },
from: 'notification@example.com'
@@ -759,6 +759,9 @@ config.action_mailer.smtp_settings = {
authentication: 'plain',
enable_starttls_auto: true }
```
+Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
+You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
+use another ESP to send email by replacing 'smpt.gmail.com' above with the address of your provider.
Mailer Testing
--------------
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index dd545b56f5..e3502d7363 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -70,12 +70,14 @@ Here's what a job looks like:
class GuestsCleanupJob < ActiveJob::Base
queue_as :default
- def perform(*args)
+ def perform(*guests)
# Do something later
end
end
```
+Note that you can define `perform` with as many arguments as you want.
+
### Enqueue the Job
Enqueue a job like so:
@@ -83,21 +85,26 @@ Enqueue a job like so:
```ruby
# Enqueue a job to be performed as soon the queuing system is
# free.
-MyJob.perform_later record
+GuestsCleanupJob.perform_later guest
```
```ruby
# Enqueue a job to be performed tomorrow at noon.
-MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record)
+GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
```
```ruby
# Enqueue a job to be performed 1 week from now.
-MyJob.set(wait: 1.week).perform_later(record)
+GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
```
-That's it!
+```ruby
+# `perform_now` and `perform_later` will call `perform` under the hood so
+# you can pass as many arguments as defined in the latter.
+GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')
+```
+That's it!
Job Execution
-------------
@@ -280,6 +287,19 @@ UserMailer.welcome(@user).deliver_later
```
+Internationalization
+--------------------
+
+Each job uses the `I18n.locale` set when the job was created. Useful if you send
+emails asynchronously:
+
+```ruby
+I18n.locale = :eo
+
+UserMailer.welcome(@user).deliver_later # Email will be localized to Esparanto.
+```
+
+
GlobalID
--------
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 4b2bfaee2f..81e2a69504 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -8,12 +8,12 @@ classes. Active Model allows for Action Pack helpers to interact with
plain Ruby objects. Active Model also helps build custom ORMs for use
outside of the Rails framework.
-After reading this guide, you will be able to add to plain Ruby objects:
+After reading this guide, you will know:
-* The ability to behave like an Active Record model.
-* Callbacks and validations like Active Record.
-* Serializers.
-* Integration with the Rails internationalization (i18n) framework.
+* How an Active Record model behaves.
+* How Callbacks and validations work.
+* How serializers work.
+* The Rails internationalization (i18n) framework.
--------------------------------------------------------------------------------
@@ -319,9 +319,8 @@ person.serializable_hash # => {"name"=>"Bob"}
#### ActiveModel::Serializers
-Rails provides two serializers `ActiveModel::Serializers::JSON` and
-`ActiveModel::Serializers::Xml`. Both of these modules automatically include
-the `ActiveModel::Serialization`.
+Rails provides a `ActiveModel::Serializers::JSON` serializer.
+This module automatically include the `ActiveModel::Serialization`.
##### ActiveModel::Serializers::JSON
@@ -379,62 +378,6 @@ person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob">
person.name # => "Bob"
```
-##### ActiveModel::Serializers::Xml
-
-To use the `ActiveModel::Serializers::Xml` you only need to change from
-`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`.
-
-```ruby
-class Person
- include ActiveModel::Serializers::Xml
-
- attr_accessor :name
-
- def attributes
- {'name' => nil}
- end
-end
-```
-
-With the `to_xml` you have an XML representing the model.
-
-```ruby
-person = Person.new
-person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n"
-person.name = "Bob"
-person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
-```
-
-From an XML string you define the attributes of the model.
-You need to have the `attributes=` method defined on your class:
-
-```ruby
-class Person
- include ActiveModel::Serializers::Xml
-
- attr_accessor :name
-
- def attributes=(hash)
- hash.each do |key, value|
- send("#{key}=", value)
- end
- end
-
- def attributes
- {'name' => nil}
- end
-end
-```
-
-Now it is possible to create an instance of person and set the attributes using `from_xml`.
-
-```ruby
-xml = { name: 'Bob' }.to_xml
-person = Person.new
-person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob">
-person.name # => "Bob"
-```
-
### Translation
`ActiveModel::Translation` provides integration between your object and the Rails
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index a227b54040..dafbe17bbd 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -142,7 +142,7 @@ end
This will create a `Product` model, mapped to a `products` table at the
database. By doing this you'll also have the ability to map the columns of each
row in that table with the attributes of the instances of your model. Suppose
-that the `products` table was created using an SQL sentence like:
+that the `products` table was created using an SQL statement like:
```sql
CREATE TABLE products (
@@ -260,7 +260,7 @@ david = User.find_by(name: 'David')
```ruby
# find all users named David who are Code Artists and sort by created_at in reverse chronological order
-users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')
+users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc)
```
You can learn more about querying an Active Record model in the [Active Record
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index ce605c912e..4e5902fb3d 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -357,8 +357,8 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
### Creating a Join Table
-Migration method `create_join_table` creates an HABTM join table. A typical use
-would be:
+The migration method `create_join_table` creates an HABTM (has and belongs to
+many) join table. A typical use would be:
```ruby
create_join_table :products, :categories
@@ -367,23 +367,21 @@ create_join_table :products, :categories
which creates a `categories_products` table with two columns called
`category_id` and `product_id`. These columns have the option `:null` set to
`false` by default. This can be overridden by specifying the `:column_options`
-option.
+option:
```ruby
-create_join_table :products, :categories, column_options: {null: true}
+create_join_table :products, :categories, column_options: { null: true }
```
-will create the `product_id` and `category_id` with the `:null` option as
-`true`.
-
-You can pass the option `:table_name` when you want to customize the table
-name. For example:
+By default, the name of the join table comes from the union of the first two
+arguments provided to create_join_table, in alphabetical order.
+To customize the name of the table, provide a `:table_name` option:
```ruby
create_join_table :products, :categories, table_name: :categorization
```
-will create a `categorization` table.
+creates a `categorization` table.
`create_join_table` also accepts a block, which you can use to add indices
(which are not created by default) or additional columns:
@@ -1006,7 +1004,10 @@ such features, the `execute` method can be used to execute arbitrary SQL.
Migrations and Seed Data
------------------------
-Some people use migrations to add data to the database:
+The main purpose of Rails' migration feature is to issue commands that modify the
+schema using a consistent process. Migrations can also be used
+to add or modify data. This is useful in an existing database that can't be destroyed
+and recreated, such as a production database.
```ruby
class AddInitialProducts < ActiveRecord::Migration
@@ -1022,9 +1023,11 @@ class AddInitialProducts < ActiveRecord::Migration
end
```
-However, Rails has a 'seeds' feature that should be used for seeding a database
-with initial data. It's a really simple feature: just fill up `db/seeds.rb`
-with some Ruby code, and run `rake db:seed`:
+To add initial data after a database is created, Rails has a built-in
+'seeds' feature that makes the process quick and easy. This is especially
+useful when reloading the database frequently in development and test environments.
+It's easy to get started with this feature: just fill up `db/seeds.rb` with some
+Ruby code, and run `rake db:seed`:
```ruby
5.times do |i|
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index fe112a4708..9d495dfacb 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -252,7 +252,7 @@ extension to use uuid.
```ruby
# db/migrate/20131220144913_create_revisions.rb
create_table :revisions do |t|
- t.column :identifier, :uuid
+ t.uuid :identifier
end
# app/models/revision.rb
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index e49abc41f4..cd44c685ba 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -244,14 +244,6 @@ INFO. The adapters will add their own data as well.
}
```
-### identity.active_record
-
-| Key | Value |
-| ---------------- | ----------------------------------------- |
-| `:line` | Primary Key of object in the identity map |
-| `:name` | Record's class |
-| `:connection_id` | `self.object_id` |
-
### instantiation.active_record
| Key | Value |
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 4a610e8458..7b8d2d3aef 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -403,13 +403,13 @@ When using the asset pipeline, paths to assets must be re-written and
underscored in Ruby) for the following asset classes: image, font, video, audio,
JavaScript and stylesheet.
-* `image-url("rails.png")` becomes `url(/assets/rails.png)`
-* `image-path("rails.png")` becomes `"/assets/rails.png"`.
+* `image-url("rails.png")` returns `url(/assets/rails.png)`
+* `image-path("rails.png")` returns `"/assets/rails.png"`
The more generic form can also be used:
-* `asset-url("rails.png")` becomes `url(/assets/rails.png)`
-* `asset-path("rails.png")` becomes `"/assets/rails.png"`
+* `asset-url("rails.png")` returns `url(/assets/rails.png)`
+* `asset-path("rails.png")` returns `"/assets/rails.png"`
#### JavaScript/CoffeeScript and ERB
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index c0fa3cfd04..1191f5edfe 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -622,6 +622,19 @@ end
We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled model IDs, or exceptions about conflicting IDs, chances are you forgot that bit.
+You can also use the method `create_join_table`
+
+```ruby
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
+ def change
+ create_join_table :assemblies, :parts do |t|
+ t.index :assembly_id
+ t.index :part_id
+ end
+ end
+end
+```
+
### Controlling Association Scope
By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 20f11c2bc2..9a56233e4a 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -3,12 +3,24 @@
Caching with Rails: An Overview
===============================
-This guide is an introduction to speeding up your Rails app with caching.
+This guide is an introduction to speeding up your Rails application with caching.
+
+Caching means to store content generated during the request-response cycle and
+to reuse it when responding to similar requests.
+
+Caching is often the most effective way to boost an application's performance.
+Through caching, web sites running on a single server with a single database
+can sustain a load of thousands of concurrent users.
+
+Rails provides a set of caching features out of the box. This guide will teach
+you the scope and purpose of each one of them. Master these techniques and your
+Rails applications can serve millions of views without exorbitant response times
+or server bills.
After reading this guide, you will know:
-* Page and action caching.
* Fragment and Russian doll caching.
+* How to manage the caching dependencies.
* Alternative cache stores.
* Conditional GET support.
@@ -32,7 +44,7 @@ config.action_controller.perform_caching = true
```
NOTE: Changing the value of `config.action_controller.perform_caching` will
-only have an effect on the caching provided by the Action Controller component.
+only have an effect on the caching provided by the Action Controller component.
For instance, it will not impact low-level caching, that we address
[below](#low-level-caching).
@@ -103,6 +115,30 @@ If you want to cache a fragment under certain conditions, you can use
<% end %>
```
+#### Collection caching
+
+The `render` helper can also cache individual templates rendered for a collection.
+It can even one up the previous example with `each` by reading all cache
+templates at once instead of one by one. This is done automatically if the template
+rendered by the collection includes a `cache` call. Take a collection that renders
+a `products/_product.html.erb` partial for each element:
+
+```ruby
+render products
+```
+
+If `products/_product.html.erb` starts with a `cache` call like so:
+
+```html+erb
+<% cache product do %>
+ <%= product.name %>
+<% end %>
+```
+
+All the cached templates from previous renders will be fetched at once with much
+greater speed. There's more info on how to make your templates [eligible for
+collection caching](http://api.rubyonrails.org/classes/ActionView/Template/Handlers/ERB.html#method-i-resource_cache_call_pattern).
+
### Russian Doll Caching
You may want to nest cached fragments inside other cached fragments. This is
@@ -152,6 +188,93 @@ With `touch` set to true, any action which changes `updated_at` for a game
record will also change it for the associated product, thereby expiring the
cache.
+### Managing dependencies
+
+In order to correctly invalidate the cache, you need to properly define the
+caching dependencies. Rails is clever enough to handle common cases so you don't
+have to specify anything. However, sometimes, when you're dealing with custom
+helpers for instance, you need to explicitly define them.
+
+#### Implicit dependencies
+
+Most template dependencies can be derived from calls to `render` in the template
+itself. Here are some examples of render calls that `ActionView::Digestor` knows
+how to decode:
+
+```ruby
+render partial: "comments/comment", collection: commentable.comments
+render "comments/comments"
+render 'comments/comments'
+render('comments/comments')
+
+render "header" => render("comments/header")
+
+render(@topic) => render("topics/topic")
+render(topics) => render("topics/topic")
+render(message.topics) => render("topics/topic")
+```
+
+On the other hand, some calls need to be changed to make caching work properly.
+For instance, if you're passing a custom collection, you'll need to change:
+
+```ruby
+render @project.documents.where(published: true)
+```
+
+to:
+
+```ruby
+render partial: "documents/document", collection: @project.documents.where(published: true)
+```
+
+#### Explicit dependencies
+
+Sometimes you'll have template dependencies that can't be derived at all. This
+is typically the case when rendering happens in helpers. Here's an example:
+
+```html+erb
+<%= render_sortable_todolists @project.todolists %>
+```
+
+You'll need to use a special comment format to call those out:
+
+```html+erb
+<%# Template Dependency: todolists/todolist %>
+<%= render_sortable_todolists @project.todolists %>
+```
+
+In some cases, like a single table inheritance setup, you might have a bunch of
+explicit dependencies. Instead of writing every template out, you can use a
+wildcard to match any template in a directory:
+
+```html+erb
+<%# Template Dependency: events/* %>
+<%= render_categorizable_events @person.events %>
+```
+
+As for collection caching, if the partial template doesn't start with a clean
+cache call, you can still benefit from collection caching by adding a special
+comment format anywhere in the template, like:
+
+```html+erb
+<%# Template Collection: notification %>
+<% my_helper_that_calls_cache(some_arg, notification) do %>
+ <%= notification.name %>
+<% end %>
+```
+
+#### External dependencies
+
+If you use a helper method, for example, inside a cached block and you then update
+that helper, you'll have to bump the cache as well. It doesn't really matter how
+you do it, but the md5 of the template file must change. One recommendation is to
+simply be explicit in a comment, like:
+
+```html+erb
+<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
+<%= some_helper_method(person) %>
+```
+
### Low-Level Caching
Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information.
@@ -207,16 +330,17 @@ persistent fashion, you can with low level caching.
Cache Stores
------------
-Rails provides different stores for the cached data created by **action** and **fragment** caches.
-
-TIP: Page caches are always stored on disk.
+Rails provides different stores for the cached data (apart from SQL and page
+caching).
### Configuration
-You can set up your application's default cache store by calling `config.cache_store=` in the Application definition inside your `config/application.rb` file or in an Application.configure block in an environment specific configuration file (i.e. `config/environments/*.rb`). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor.
+You can set up your application's default cache store by setting the
+`config.cache_store` configuration option. Other parameters can be passed as
+arguments to the cache store's constructor:
```ruby
-config.cache_store = :memory_store
+config.cache_store = :memory_store, { size: 64.megabytes }
```
NOTE: Alternatively, you can call `ActionController::Base.cache_store` outside of a configuration block.
@@ -241,6 +365,19 @@ There are some common options used by all cache implementations. These can be pa
* `:race_condition_ttl` - This option is used in conjunction with the `:expires_in` option. It will prevent race conditions when cache entries expire by preventing multiple processes from simultaneously regenerating the same entry (also known as the dog pile effect). This option sets the number of seconds that an expired entry can be reused while a new value is being regenerated. It's a good practice to set this value if you use the `:expires_in` option.
+#### Custom Cache Stores
+
+You can create your own custom cache store by simply extending
+`ActiveSupport::Cache::Store` and implementing the appropriate methods. This way,
+you can swap in any number of caching technologies into your Rails application.
+
+To use a custom cache store, simply set the cache store to a new instance of your
+custom class.
+
+```ruby
+config.cache_store = MyCacheStore.new
+```
+
### ActiveSupport::Cache::MemoryStore
This cache store keeps entries in memory in the same Ruby process. The cache
@@ -292,36 +429,6 @@ The `write` and `fetch` methods on this cache accept two additional options that
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
```
-### ActiveSupport::Cache::EhcacheStore
-
-If you are using JRuby you can use Terracotta's Ehcache as the cache store for your application. Ehcache is an open source Java cache that also offers an enterprise version with increased scalability, management, and commercial support. You must first install the jruby-ehcache-rails3 gem (version 1.1.0 or later) to use this cache store.
-
-```ruby
-config.cache_store = :ehcache_store
-```
-
-When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache).
-
-In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including:
-
-| Property | Argument Type | Description |
-| --------------------------- | ------------------- | ----------------------------------------------------------- |
-| elementEvictionData | ElementEvictionData | Sets this element's eviction data instance. |
-| eternal | boolean | Sets whether the element is eternal. |
-| timeToIdle, tti | int | Sets time to idle |
-| timeToLive, ttl, expires_in | int | Sets time to Live |
-| version | long | Sets the version attribute of the ElementAttributes object. |
-
-These options are passed to the `write` method as Hash options using either camelCase or underscore notation, as in the following examples:
-
-```ruby
-Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds)
-caches_action :index, expires_in: 60.seconds, unless_exist: true
-```
-
-For more information about Ehcache, see [http://ehcache.org/](http://ehcache.org/) .
-For more information about Ehcache for JRuby and Rails, see [http://ehcache.org/documentation/jruby.html](http://ehcache.org/documentation/jruby.html)
-
### ActiveSupport::Cache::NullStore
This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
@@ -330,19 +437,13 @@ This cache store implementation is meant to be used only in development or test
config.cache_store = :null_store
```
-### Custom Cache Stores
-
-You can create your own custom cache store by simply extending `ActiveSupport::Cache::Store` and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application.
-
-To use a custom cache store, simply set the cache store to a new instance of the class.
-
-```ruby
-config.cache_store = MyCacheStore.new
-```
-
-### Cache Keys
+Cache Keys
+----------
-The keys used in a cache can be any object that responds to either `:cache_key` or `:to_param`. You can implement the `:cache_key` method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id.
+The keys used in a cache can be any object that responds to either `cache_key` or
+`to_param`. You can implement the `cache_key` method on your classes if you need
+to generate custom keys. Active Record will generate keys based on the class name
+and record id.
You can use Hashes and Arrays of values as cache keys.
@@ -351,7 +452,12 @@ You can use Hashes and Arrays of values as cache keys.
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
```
-The keys you use on `Rails.cache` will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with `Rails.cache` and then try to pull them out with the `memcache-client` gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules.
+The keys you use on `Rails.cache` will not be the same as those actually used with
+the storage engine. They may be modified with a namespace or altered to fit
+technology backend constraints. This means, for instance, that you can't save
+values with `Rails.cache` and then try to pull them out with the `dalli` gem.
+However, you also don't need to worry about exceeding the memcached size limit or
+violating syntax rules.
Conditional GET support
-----------------------
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 0f5a9e4e39..cd265331d6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -260,7 +260,13 @@ $ bin/rake db:migrate
== CreateHighScores: migrated (0.0019s) ======================================
```
-INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment.
+INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions
+about code. In unit testing, we take a little part of code, say a method of a model,
+and test its inputs and outputs. Unit tests are your friend. The sooner you make
+peace with the fact that your quality of life will drastically increase when you unit
+test your code, the better. Seriously. Please visit
+[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth
+look at unit testing.
Let's see the interface Rails created for us.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 47c275609e..df9704830e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -451,6 +451,9 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.raise_on_missing_translations` determines whether an
error should be raised for missing translations.
+* `config.action_view.automatically_disable_submit_tag` determines whether
+ submit_tag should automatically disable on click, this defaults to true.
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 3b944f1274..ba82713266 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -15,6 +15,9 @@ After reading this guide, you will know:
Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
+As mentioned in [Rails
+README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-project’s codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](https://github.com/rails/rails/blob/master/CODE_OF_CONDUCT.md).
+
--------------------------------------------------------------------------------
Reporting an Issue
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index dc1df8f229..c486009741 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -346,22 +346,11 @@ by asking the debugger for help. Type: `help`
```
(byebug) help
-byebug 2.7.0
+ h[elp][ <cmd>[ <subcmd>]]
-Type 'help <command-name>' for help on a specific command
-
-Available commands:
-backtrace delete enable help list pry next restart source up
-break disable eval info method ps save step var
-catch display exit interrupt next putl set thread
-condition down finish irb p quit show trace
-continue edit frame kill pp reload skip undisplay
-```
-
-TIP: To view the help menu for any command use `help <command-name>` at the
-debugger prompt. For example: _`help list`_. You can abbreviate any debugging
-command by supplying just enough letters to distinguish them from other
-commands. For example, you can use `l` for the `list` command.
+ help -- prints this help.
+ help <cmd> -- prints help on command <cmd>.
+ help <cmd> <subcmd> -- prints help on <cmd>'s subcommand <subcmd>.
To see the previous ten lines you should type `list-` (or `l-`).
@@ -502,7 +491,7 @@ current context:
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
- :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
:@_response_body, :@marked_for_same_origin_verification, :@_config]
```
@@ -533,7 +522,7 @@ And then ask again for the instance_variables:
```
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
- :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
:@_response_body, :@marked_for_same_origin_verification, :@_config,
:@articles]
```
@@ -773,8 +762,8 @@ environment variable. A specific _line_ can also be given.
### Quitting
-To exit the debugger, use the `quit` command (abbreviated `q`), or its alias
-`exit`.
+To exit the debugger, use the `quit` command (abbreviated to `q`). Or, type `q!`
+to bypass the `Really quit? (y/n)` prompt and exit unconditionally.
A simple quit tries to terminate all threads in effect. Therefore your server
will be stopped and you will have to start it again.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 9145aee009..4473eba478 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -122,6 +122,10 @@
url: autoloading_and_reloading_constants.html
description: This guide documents how autoloading and reloading constants work.
-
+ name: "Caching with Rails: An Overview"
+ url: caching_with_rails.html
+ description: This guide is an introduction to speeding up your Rails application with caching.
+ -
name: Active Support Instrumentation
work_in_progress: true
url: active_support_instrumentation.html
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index dbbedc49ab..ef66b75ec1 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -37,7 +37,6 @@ curve diving straight into Rails. There are several curated lists of online reso
for learning Ruby:
* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
-* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby)
Be aware that some resources, while still excellent, cover versions of Ruby as old as
1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
@@ -92,17 +91,17 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
dollar sign `$` should be run in the command line. Verify that you have a
current version of Ruby installed:
+```bash
+$ ruby -v
+ruby 2.2.2p95
+```
+
TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
For more installation methods for most Operating Systems take a look at
[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/).
-```bash
-$ ruby -v
-ruby 2.2.2p95
-```
-
Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
On Windows, if you installed Rails through Rails Installer, you
already have SQLite installed. Others can find installation instructions
@@ -166,7 +165,7 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 02763eabcd..e4799d93fa 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -83,7 +83,9 @@ Rails would dispatch that request to the `destroy` method on the `photos` contro
### CRUD, Verbs, and Actions
-In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as:
+In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to
+controller actions. By convention, each action also maps to a specific CRUD
+operation in a database. A single entry in the routing file, such as:
```ruby
resources :photos
@@ -615,6 +617,8 @@ get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`.
+NOTE: You cannot override defaults via query parameters - this is for security reasons. The only defaults that can be overridden are dynamic segments via substitution in the URL path.
+
### Naming Routes
You can specify a name for any route using the `:as` option:
@@ -793,7 +797,11 @@ get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params
get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
```
-Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
+Please note that default redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. You can use the `:status` option to change the response status:
+
+```ruby
+get '/stories/:name', to: redirect('/articles/%{name}', status: 302)
+```
In all of these cases, if you don't provide the leading host (`http://www.example.com`), Rails will take those details from the current request.
@@ -1087,12 +1095,12 @@ edit_videos GET /videos/:identifier/edit(.:format) videos#edit
Video.find_by(identifier: params[:identifier])
```
-You can override `ActiveRecord::Base#to_param` of a related
-model to constructe an URL.
+You can override `ActiveRecord::Base#to_param` of a related model to construct
+an URL:
```ruby
class Video < ActiveRecord::Base
- def to_param # overridden
+ def to_param
identifier
end
end
diff --git a/guides/source/security.md b/guides/source/security.md
index 485b108d12..850d111bd7 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -93,9 +93,16 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
* Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_.
-* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie.
+* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_.
+However, since Rails 4, the default store is EncryptedCookieStore. With
+EncryptedCookieStore the session is encrypted before being stored in a cookie.
+This prevents the user from accessing and tampering the content of the cookie.
+Thus the session becomes a more secure place to store data. The encryption is
+done using a server-side secret key `secrets.secret_key_base` stored in
+`config/secrets.yml`.
+
+That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rake secret` instead_.
`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
@@ -191,11 +198,10 @@ This attack method works by including malicious code or a link in a page that ac
In the [session chapter](#sessions) you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is, that it will also send the cookie, if the request comes from a site of a different domain. Let's start with an example:
-* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file.
-* `<img src="http://www.webapp.com/project/1/destroy">`
-* Bob's session at www.webapp.com is still alive, because he didn't log out a few minutes ago.
-* By viewing the post, the browser finds an image tag. It tries to load the suspected image from www.webapp.com. As explained before, it will also send along the cookie with the valid session id.
-* The web application at www.webapp.com verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.
+* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: `<img src="http://www.webapp.com/project/1/destroy">`
+* Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago.
+* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session id.
+* The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.
* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone.
It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email.
@@ -220,7 +226,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST (
If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier.
-_POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.
+_POST requests can be sent automatically, too_. Here is an example for a link which displays `www.harmless.com` as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.
```html
<a href="http://www.harmless.com/" onclick="
@@ -1008,18 +1014,12 @@ config.action_dispatch.default_headers.clear
Here is a list of common headers:
-* X-Frame-Options
-_'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website.
-* X-XSS-Protection
-_'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS attack is detected. Set it to '0;' if you want to switch XSS Auditor off(useful if response contents scripts from request parameters)
-* X-Content-Type-Options
-_'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file.
-* X-Content-Security-Policy
-[A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html)
-* Access-Control-Allow-Origin
-Used to control which sites are allowed to bypass same origin policies and send cross-origin requests.
-* Strict-Transport-Security
-[Used to control if the browser is allowed to only access a site over a secure connection](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
+* **X-Frame-Options:** _'SAMEORIGIN' in Rails by default_ - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website.
+* **X-XSS-Protection:** _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS attack is detected. Set it to '0;' if you want to switch XSS Auditor off(useful if response contents scripts from request parameters)
+* **X-Content-Type-Options:** _'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file.
+* **X-Content-Security-Policy:** [A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html)
+* **Access-Control-Allow-Origin:** Used to control which sites are allowed to bypass same origin policies and send cross-origin requests.
+* **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
Environmental Security
----------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index f40e765242..3a691220cf 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -25,15 +25,7 @@ Rails tests can also simulate browser requests and thus you can test your applic
Introduction to Testing
-----------------------
-Testing support was woven into the Rails fabric from the beginning. It wasn't an "oh! let's bolt on support for running tests because they're new and cool" epiphany. Just about every Rails application interacts heavily with a database and, as a result, your tests will need a database to interact with as well. To write efficient tests, you'll need to understand how to set up this database and populate it with sample data.
-
-### The Test Environment
-
-By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`.
-
-A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases.
-
-Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`.
+Testing support was woven into the Rails fabric from the beginning. It wasn't an "oh! let's bolt on support for running tests because they're new and cool" epiphany.
### Rails Sets up for Testing from the Word Go
@@ -51,123 +43,18 @@ Fixtures are a way of organizing test data; they reside in the `fixtures` direct
The `test_helper.rb` file holds the default configuration for your tests.
-### The Low-Down on Fixtures
-
-For good tests, you'll need to give some thought to setting up test data.
-In Rails, you can handle this by defining and customizing fixtures.
-You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
-
-#### What Are Fixtures?
-
-_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model.
-
-You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model, Rails automatically creates fixture stubs in this directory.
-
-#### YAML
-
-YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
-
-Here's a sample YAML fixture file:
-
-```yaml
-# lo & behold! I am a YAML comment!
-david:
- name: David Heinemeier Hansson
- birthday: 1979-10-15
- profession: Systems development
-
-steve:
- name: Steve Ross Kellock
- birthday: 1974-09-27
- profession: guy with keyboard
-```
-
-Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank line. You can place comments in a fixture file by using the # character in the first column.
-
-If you are working with [associations](/association_basics.html), you can simply
-define a reference node between two different fixtures. Here's an example with
-a `belongs_to`/`has_many` association:
-
-```yaml
-# In fixtures/categories.yml
-about:
- name: About
-
-# In fixtures/articles.yml
-one:
- title: Welcome to Rails!
- body: Hello world!
- category: about
-```
-
-Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
-
-NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
-
-#### ERB'in It Up
-
-ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users:
-
-```erb
-<% 1000.times do |n| %>
-user_<%= n %>:
- username: <%= "user#{n}" %>
- email: <%= "user#{n}@example.com" %>
-<% end %>
-```
-
-#### Fixtures in Action
-
-Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps:
-
-1. Remove any existing data from the table corresponding to the fixture
-2. Load the fixture data into the table
-3. Dump the fixture data into a method in case you want to access it directly
-
-TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html))
-#### Fixtures are Active Record objects
-
-Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example:
-
-```ruby
-# this will return the User object for the fixture named david
-users(:david)
-
-# this will return the property for david called id
-users(:david).id
-
-# one can also access methods available on the User class
-email(david.partner.email, david.location_tonight)
-```
-
-To get multiple fixtures at once, you can pass in a list of fixture names. For example:
-
-```ruby
-# this will return an array containing the fixtures david and steve
-users(:david, :steve)
-```
-
-### Console Tasks for Running your Tests
-
-Rails comes with a CLI command to run tests.
-Here are some examples of how to use it:
+### The Test Environment
-```bash
-$ bin/rails test # run all tests in the `test` directory
-$ bin/rails test test/controllers # run all tests from specific directory
-$ bin/rails test test/models/post_test.rb # run specific test
-$ bin/rails test test/models/post_test.rb:44 # run specific test and line
-```
+By default, every Rails application has three environments: development, test, and production.
-We will cover each of types Rails tests listed above in this guide.
+Each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`.
-Model Testing
-------------------------
+NOTE: Your test are run under RAILS_ENV=test.
-For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide.
+### Rails meets Minitest
-If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory:
+If you remember when you used the `rails generate scaffold` command from the [Getting Started with Rails](getting_started.html) guide. We created our first resource among other things it created test stubs in the `test` directory:
```bash
$ bin/rails generate scaffold article title:string body:text
@@ -178,14 +65,6 @@ create test/fixtures/articles.yml
...
```
-You can also generate the test stub for a model using the following command:
-
-```bash
-$ bin/rails generate test_unit:model article title:string body:text
-create test/models/article_test.rb
-create test/fixtures/articles.yml
-```
-
The default test stub in `test/models/article_test.rb` looks like this:
```ruby
@@ -250,47 +129,6 @@ An assertion is a line of code that evaluates an object (or expression) for expe
Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass.
-### Maintaining the test database schema
-
-In order to run your tests, your test database will need to have the current
-structure. The test helper checks whether your test database has any pending
-migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
-into the test database. If migrations are still pending, an error will be
-raised. Usually this indicates that your schema is not fully migrated. Running
-the migrations against the development database (`bin/rake db:migrate`) will
-bring the schema up to date.
-
-NOTE: If existing migrations required modifications, the test database needs to
-be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
-
-### Running Tests
-
-Running a test is as simple as invoking the file containing the test cases through `rails test` command.
-
-```bash
-$ bin/rails test test/models/article_test.rb
-.
-
-Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
-
-1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
-```
-
-This will run all test methods from the test case.
-
-You can also run a particular test method from the test case by running the test and providing the `test method name`.
-
-```bash
-$ bin/rails test test/models/article_test.rb test_the_truth
-.
-
-Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
-
-1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
-```
-
-The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary.
-
#### Your first failing test
To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case.
@@ -443,8 +281,8 @@ specify to make your test failure messages clearer. It's not required.
| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.|
| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
-| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
-| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
+| `assert_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
+| `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
| `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.|
@@ -485,7 +323,7 @@ Rails adds some custom assertions of its own to the `minitest` framework:
You'll see the usage of some of these assertions in the next chapter.
-### A Brief Note About Minitest
+### A Brief Note About Test Cases
All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from:
@@ -500,6 +338,297 @@ Each of these classes include `Minitest::Assertions`, allowing us to use all of
NOTE: For more information on `Minitest`, refer to [Minitest](http://docs.seattlerb.org/minitest)
+### The Rails Test Runner
+
+We can run all of our tests at once by using the `rails test` command.
+
+Or we can run a single test by passing the `rails test` command the filename containing the test cases.
+
+```bash
+$ bin/rails test test/models/article_test.rb
+.
+
+Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
+
+1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
+```
+
+This will run all test methods from the test case.
+
+You can also run a particular test method from the test case by providing the `-n` or `--name` flag and the `test method name`.
+
+```bash
+$ bin/rails test test/models/article_test.rb -n test_the_truth
+.
+
+Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
+
+1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
+```
+
+You can also run a test at a specific line by providing the line number.
+
+```bash
+$ bin/rails test test/models/post_test.rb:44 # run specific test and line
+```
+
+You can also run an entire directory of tests by providing the path to the directory.
+
+```bash
+$ bin/rails test test/controllers # run all tests from specific directory
+```
+
+
+The Test Database
+-----------------
+
+Just about every Rails application interacts heavily with a database and, as a result, your tests will need a database to interact with as well. To write efficient tests, you'll need to understand how to set up this database and populate it with sample data.
+
+By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`.
+
+A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases.
+
+
+### Maintaining the test database schema
+
+In order to run your tests, your test database will need to have the current
+structure. The test helper checks whether your test database has any pending
+migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
+into the test database. If migrations are still pending, an error will be
+raised. Usually this indicates that your schema is not fully migrated. Running
+the migrations against the development database (`bin/rake db:migrate`) will
+bring the schema up to date.
+
+NOTE: If existing migrations required modifications, the test database needs to
+be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
+
+### The Low-Down on Fixtures
+
+For good tests, you'll need to give some thought to setting up test data.
+In Rails, you can handle this by defining and customizing fixtures.
+You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+
+#### What Are Fixtures?
+
+_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model.
+
+You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model, Rails automatically creates fixture stubs in this directory.
+
+#### YAML
+
+YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
+
+Here's a sample YAML fixture file:
+
+```yaml
+# lo & behold! I am a YAML comment!
+david:
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+
+steve:
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+```
+
+Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank line. You can place comments in a fixture file by using the # character in the first column.
+
+If you are working with [associations](/association_basics.html), you can simply
+define a reference node between two different fixtures. Here's an example with
+a `belongs_to`/`has_many` association:
+
+```yaml
+# In fixtures/categories.yml
+about:
+ name: About
+
+# In fixtures/articles.yml
+one:
+ title: Welcome to Rails!
+ body: Hello world!
+ category: about
+```
+
+Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
+
+NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+
+#### ERB'in It Up
+
+ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users:
+
+```erb
+<% 1000.times do |n| %>
+user_<%= n %>:
+ username: <%= "user#{n}" %>
+ email: <%= "user#{n}@example.com" %>
+<% end %>
+```
+
+#### Fixtures in Action
+
+Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps:
+
+1. Remove any existing data from the table corresponding to the fixture
+2. Load the fixture data into the table
+3. Dump the fixture data into a method in case you want to access it directly
+
+TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html))
+
+#### Fixtures are Active Record objects
+
+Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example:
+
+```ruby
+# this will return the User object for the fixture named david
+users(:david)
+
+# this will return the property for david called id
+users(:david).id
+
+# one can also access methods available on the User class
+email(david.partner.email, david.location_tonight)
+```
+
+To get multiple fixtures at once, you can pass in a list of fixture names. For example:
+
+```ruby
+# this will return an array containing the fixtures david and steve
+users(:david, :steve)
+```
+
+
+Model Testing
+-------------
+
+Model tests are used to test the various models of your application.
+
+For creating Rails model tests, we use the 'test/model' directory for your application. Rails provides a generator to create an model test skeleton for you.
+
+```bash
+$ bin/rails generate test_unit:model article title:string body:text
+create test/models/article_test.rb
+create test/fixtures/articles.yml
+```
+
+Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from `ActiveSupport::TestCase`.
+
+
+Integration Testing
+-------------------
+
+Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
+
+For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
+
+```bash
+$ bin/rails generate integration_test user_flows
+ exists test/integration/
+ create test/integration/user_flows_test.rb
+```
+
+Here's what a freshly-generated integration test looks like:
+
+```ruby
+require 'test_helper'
+
+class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "the truth" do
+ # assert true
+ # end
+end
+```
+
+Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
+
+### Helpers Available for Integration Tests
+
+In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
+
+For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
+
+When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
+
+If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
+
+### Implementing an integration test
+
+Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly.
+
+We'll start by generating our integration test skeleton:
+
+```bash
+$ bin/rails generate integration_test blog_flow
+```
+
+It should have created a test file placeholder for us, with the output of the previous command you should see:
+
+```bash
+ invoke test_unit
+ create test/integration/blog_flow_test.rb
+```
+
+Now let's open that file and write our first assertion:
+
+```ruby
+require 'test_helper'
+
+class BlogFlowTest < ActionDispatch::IntegrationTest
+ test "can see the welcome page" do
+ get "/"
+ assert_select "h1", "Welcome#index"
+ end
+end
+```
+
+If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
+
+When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
+
+#### Creating articles integration
+
+How about testing our ability to create a new article in our blog and see the resulting article.
+
+```ruby
+test "can create an article" do
+ get "/articles/new"
+ assert_response :success
+
+ post "/articles",
+ params: { article: { title: "can create", body: "article successfully." } }
+ assert_response :redirect
+ follow_redirect!
+ assert_response :success
+ assert_select "p", "Title:\n can create"
+end
+```
+
+Let's break this test down so we can understand it.
+
+We start by calling the `:new` action on our Articles controller. This response should be successful.
+
+After this we make a post request to the `:create` action of our Articles controller:
+
+```ruby
+post "/articles",
+ params: { article: { title: "can create", body: "article successfully." } }
+assert_response :redirect
+follow_redirect!
+```
+
+The two lines following the request are to handle the redirect we setup when creating a new article.
+
+NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made.
+
+Finally we can assert that our response was successful and our new article is readable on the page.
+
+#### Taking it further
+
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
+
+
Functional Tests for Your Controllers
-------------------------------------
@@ -612,9 +741,9 @@ test "ajax request" do
end
```
-### The Four Hashes of the Apocalypse
+### The Three Hashes of the Apocalypse
-After a request has been made and processed, you will have 4 Hash objects ready for use:
+After a request has been made and processed, you will have 3 Hash objects ready for use:
* `cookies` - Any cookies that are set.
* `flash` - Any objects living in the flash.
@@ -655,7 +784,7 @@ post :create # simulate the request with custom env variable
### Testing `flash` notices
-If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`.
+If you remember from earlier one of the Three Hashes of the Apocalypse was `flash`.
We want to add a `flash` message to our blog application whenever someone
successfully creates a new Article.
@@ -844,33 +973,7 @@ end
Testing Routes
--------------
-Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
-
-```ruby
-class ArticleRoutesTest < ActionController::TestCase
- test "should route to article" do
- assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
- end
-
- test "should route to create article" do
- assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
- end
-end
-```
-
-I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see:
-
-```bash
-$ bin/rails test test/controllers/articles_routes_test.rb
-
-# Running:
-
-..
-
-Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s.
-
-2 runs, 6 assertions, 0 failures, 0 errors, 0 skips
-```
+Like everything else in your Rails application, you can test your routes.
For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html).
@@ -960,8 +1063,6 @@ have to use a mixin like this:
```ruby
class UserHelperTest < ActionView::TestCase
- include UserHelper
-
test "should return the user name" do
# ...
end
@@ -971,118 +1072,6 @@ end
Moreover, since the test class extends from `ActionView::TestCase`, you have
access to Rails' helper methods such as `link_to` or `pluralize`.
-Integration Testing
--------------------
-
-Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
-
-For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
-
-```bash
-$ bin/rails generate integration_test user_flows
- exists test/integration/
- create test/integration/user_flows_test.rb
-```
-
-Here's what a freshly-generated integration test looks like:
-
-```ruby
-require 'test_helper'
-
-class UserFlowsTest < ActionDispatch::IntegrationTest
- # test "the truth" do
- # assert true
- # end
-end
-```
-
-Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
-
-### Helpers Available for Integration Tests
-
-In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
-
-For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
-
-When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
-
-If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
-
-### Implementing an integration test
-
-Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly.
-
-We'll start by generating our integration test skeleton:
-
-```bash
-$ bin/rails generate integration_test blog_flow
-```
-
-It should have created a test file placeholder for us, with the output of the previous command you should see:
-
-```bash
- invoke test_unit
- create test/integration/blog_flow_test.rb
-```
-
-Now let's open that file and write our first assertion:
-
-```ruby
-require 'test_helper'
-
-class BlogFlowTest < ActionDispatch::IntegrationTest
- test "can see the welcome page" do
- get "/"
- assert_select "h1", "Welcome#index"
- end
-end
-```
-
-If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
-
-When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
-
-#### Creating articles integration
-
-How about testing our ability to create a new article in our blog and see the resulting article.
-
-```ruby
-test "can create an article" do
- get "/articles/new"
- assert_response :success
-
- post "/articles",
- params: { article: { title: "can create", body: "article successfully." } }
- assert_response :redirect
- follow_redirect!
- assert_response :success
- assert_select "p", "Title:\n can create"
-end
-```
-
-Let's break this test down so we can understand it.
-
-We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial.
-
-After this we make a post request to the `:create` action of our Articles controller:
-
-```ruby
-post "/articles",
- params: { article: { title: "can create", body: "article successfully." } }
-assert_response :redirect
-follow_redirect!
-```
-
-The two lines following the request are to handle the redirect we setup when creating a new article.
-
-NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made.
-
-Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page.
-
-#### Taking it further
-
-We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
-
Testing Your Mailers
--------------------
@@ -1235,16 +1224,3 @@ class ProductTest < ActiveJob::TestCase
end
end
```
-
-Other Testing Approaches
-------------------------
-
-The built-in `minitest` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
-
-* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
-* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
-* [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run.
-* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
-* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
-* [RSpec](http://relishapp.com/rspec), a behavior-driven development framework
-* [Capybara](http://jnicklas.github.com/capybara/), Acceptance test framework for web applications
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 17309d4b47..743241d7a0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -75,7 +75,7 @@ warning by adding the following configuration to your `config/application.rb`:
See [#17227](https://github.com/rails/rails/pull/17227) for more details.
-### ActiveJob jobs now inherent from ApplicationJob by default
+### ActiveJob jobs now inherit from ApplicationJob by default
In Rails 4.2 an ActiveJob inherits from `ActiveJob::Base`. In Rails 5.0 this
behavior has changed to now inherit from `ApplicationJob`.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 4c996dd2d0..1c42ff2914 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -357,7 +357,7 @@ This gem uses Ajax to speed up page rendering in most applications.
Turbolinks attaches a click handler to all `<a>` on the page. If your browser
supports
-[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState()_method),
+[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState%28%29_method),
Turbolinks will make an Ajax request for the page, parse the response, and
replace the entire `<body>` of the page with the `<body>` of the response. It
will then use PushState to change the URL to the correct one, preserving
diff --git a/install.rb b/install.rb
deleted file mode 100644
index 933e5ee9a2..0000000000
--- a/install.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-version = ARGV.pop
-
-if version.nil?
- puts "Usage: ruby install.rb version"
- exit(64)
-end
-
-%w( activesupport activemodel activerecord actionpack actionview actionmailer railties activejob ).each do |framework|
- puts "Installing #{framework}..."
- `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-document && rm #{framework}-#{version}.gem`
-end
-
-puts "Installing rails..."
-`gem build rails.gemspec`
-`gem install rails-#{version}.gem --no-document `
-`rm rails-#{version}.gem`
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 30449aa670..21adac272d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,37 @@
+* Plugins generated using `rails plugin new` are now generated with the
+ version number set to 0.1.0.
+
+ *Daniel Morris*
+
+* `I18n.load_path` is now reloaded under development so there's no need to
+ restart the server to make new locale files available. Also, I18n will no
+ longer raise for deleted locale files.
+
+ *Kir Shatrov*
+
+* Add `bin/update` script to update development environment automatically.
+
+ *Mehmet Emin İNAÇ*
+
+* Fix STATS_DIRECTORIES already defined warning when running rake from within
+ the top level directory of an engine that has a test app.
+
+ Fixes #20510
+
+ *Ersin Akinci*
+
+* Make enabling or disabling caching in development mode possible with
+ rake dev:cache.
+
+ Running rake dev:cache will create or remove tmp/caching-dev.txt. When this
+ file exists config.action_controller.perform_caching will be set to true in
+ config/environments/development.rb.
+
+ Additionally, a server can be started with either --dev-caching or
+ --no-dev-caching included to toggle caching on startup.
+
+ *Jussi Mertanen*, *Chuck Callebs*
+
* Add a `--api` option in order to generate plugins that can be added
inside an API application.
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0f4d932749..9baf8aa742 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -63,7 +63,7 @@ INFO
Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store)
if Rails.cache.respond_to?(:middleware)
- config.middleware.insert_before("Rack::Runtime", Rails.cache.middleware)
+ config.middleware.insert_before(::Rack::Runtime, Rails.cache.middleware)
end
end
end
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index 9a29ec21cf..618a09a5b3 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -6,7 +6,7 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
def require_local!
unless local_request?
- render text: '<p>For security purposes, this information is only available to local requests.</p>', status: :forbidden
+ render html: '<p>For security purposes, this information is only available to local requests.</p>'.html_safe, status: :forbidden
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index d1e445ac70..c8fb58ab05 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -34,6 +34,9 @@ module Rails
opts.on("-P", "--pid=pid", String,
"Specifies the PID file.",
"Default: tmp/pids/server.pid") { |v| options[:pid] = v }
+ opts.on("-C", "--[no-]dev-caching",
+ "Specifies whether to perform caching in development.",
+ "true or false") { |v| options[:caching] = v }
opts.separator ""
@@ -67,6 +70,7 @@ module Rails
print_boot_information
trap(:INT) { exit }
create_tmp_directories
+ setup_dev_caching
log_to_stdout if options[:log_stdout]
super
@@ -86,12 +90,23 @@ module Rails
DoNotReverseLookup: true,
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
+ caching: false,
pid: File.expand_path("tmp/pids/server.pid")
})
end
private
+ def setup_dev_caching
+ return unless options[:environment] == "development"
+
+ if options[:caching] == false
+ delete_cache_file
+ elsif options[:caching]
+ create_cache_file
+ end
+ end
+
def print_boot_information
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
@@ -101,6 +116,14 @@ module Rails
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
+ def create_cache_file
+ FileUtils.touch("tmp/caching-dev.txt")
+ end
+
+ def delete_cache_file
+ FileUtils.rm("tmp/caching-dev.txt") if File.exist?("tmp/caching-dev.txt")
+ end
+
def create_tmp_directories
%w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 1dede32dd4..e90d41cbec 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -519,17 +519,15 @@ module Rails
# Define the Rack API for this engine.
def call(env)
env.merge!(env_config)
- if env['SCRIPT_NAME']
- env[routes.env_key] = env['SCRIPT_NAME'].dup
- end
+ req = ActionDispatch::Request.new env
+ req.routes = routes
+ req.engine_script_name = req.script_name
app.call(env)
end
# Defines additional Rack env configuration that is added on each call.
def env_config
- @env_config ||= {
- 'action_dispatch.routes' => routes
- }
+ @env_config ||= {}
end
# Defines the routes for this engine. If a block is given to
@@ -589,7 +587,7 @@ module Rails
# I18n load paths are a special case since the ones added
# later have higher priority.
initializer :add_locales do
- config.i18n.railties_load_path.concat(paths["config/locales"].existent)
+ config.i18n.railties_load_path << paths["config/locales"]
end
initializer :add_view_paths do
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 249fe96772..38074e80e0 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -209,13 +209,19 @@ module Rails
[
GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'),
- GemfileEntry.github('arel', 'rails/arel')
+ GemfileEntry.github('sprockets', 'rails/sprockets'),
+ GemfileEntry.github('sass-rails', 'rails/sass-rails'),
+ GemfileEntry.github('arel', 'rails/arel'),
+ GemfileEntry.github('rack', 'rack/rack')
]
elsif options.edge?
[
GemfileEntry.github('rails', 'rails/rails'),
GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'),
- GemfileEntry.github('arel', 'rails/arel')
+ GemfileEntry.github('sprockets', 'rails/sprockets'),
+ GemfileEntry.github('sass-rails', 'rails/sass-rails'),
+ GemfileEntry.github('arel', 'rails/arel'),
+ GemfileEntry.github('rack', 'rack/rack')
]
else
[GemfileEntry.version('rails',
@@ -255,8 +261,6 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
- gems << GemfileEntry.version('sass-rails', '~> 5.0',
- 'Use SCSS for stylesheets')
gems << GemfileEntry.version('uglifier',
'>= 1.3.0',
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index cb86978d4c..c88426ec06 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -5,7 +5,7 @@
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/manifest.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/manifest.js.tt
new file mode 100644
index 0000000000..3325553f57
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/manifest.js.tt
@@ -0,0 +1,8 @@
+
+<% unless options.api? -%>
+//= link_tree ./images
+<% end -%>
+<% unless options.skip_javascript -%>
+//= link ./javascripts/application.js
+<% end -%>
+//= link ./stylesheets/application.css
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 0cdd2788d0..0ebd7fe829 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -7,7 +7,8 @@
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update
new file mode 100644
index 0000000000..9830e6b29a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update
@@ -0,0 +1,28 @@
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system 'bundle check' or system! 'bundle install'
+
+ puts "\n== Updating database =="
+ system! 'bin/rake db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rake log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rake restart'
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index ecb5d4170f..e29f0bacaa 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -9,9 +9,19 @@ Rails.application.configure do
# Do not eager load code on boot.
config.eager_load = false
- # Show full error reports and disable caching.
+ # Show full error reports.
config.consider_all_requests_local = true
- config.action_controller.perform_caching = false
+
+ # Enable/disable caching. By default caching is disabled.
+ if Rails.root.join('tmp/caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+ config.static_cache_control = "public, max-age=172800"
+ config.cache_store = :memory_store
+ else
+ config.action_controller.perform_caching = false
+ config.cache_store = :null_store
+ end
+
<%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
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 8c09396fc1..0297ab75f6 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
@@ -51,7 +51,8 @@ Rails.application.configure do
# config.log_tags = [ :subdomain, :request_id ]
# Use a different logger for distributed setups.
- # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 3f66539d54..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,56 +1,3 @@
Rails.application.routes.draw do
- # The priority is based upon order of creation: first created -> highest priority.
- # See how all your routes lay out with "rake routes".
-
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
-
- # Example of regular route:
- # get 'products/:id' => 'catalog#view'
-
- # Example of named route that can be invoked with purchase_url(id: product.id)
- # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
-
- # Example resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Example resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Example resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Example resource route with more complex sub-resources:
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', on: :collection
- # end
- # end
-
- # Example resource route with concerns:
- # concern :toggleable do
- # post 'toggle'
- # end
- # resources :posts, concerns: :toggleable
- # resources :photos, concerns: :toggleable
-
- # Example resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 0e1326ce4f..8fea30189e 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -104,8 +104,9 @@ task default: :test
end
def test_dummy_assets
- template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true
- template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true
+ template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true
+ template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true
+ template "rails/dummy_manifest.js", "#{dummy_path}/app/assets/manifest.js", force: true
end
def test_dummy_clean
@@ -122,6 +123,10 @@ task default: :test
end
end
+ def assets_manifest
+ template "rails/engine_manifest.js", "app/assets/#{underscored_name}_manifest.js"
+ end
+
def stylesheets
if mountable?
copy_file "rails/stylesheets.css",
@@ -220,6 +225,10 @@ task default: :test
build(:lib)
end
+ def create_assets_manifest_file
+ build(:assets_manifest) unless api?
+ end
+
def create_public_stylesheets_files
build(:stylesheets) unless api?
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
index d257295988..b08f4ef9ae 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
@@ -1 +1 @@
-<%= wrap_in_modules 'VERSION = "0.0.1"' %>
+<%= wrap_in_modules "VERSION = '0.1.0'" %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js
new file mode 100644
index 0000000000..ace695a811
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js
@@ -0,0 +1,11 @@
+
+<% unless api? -%>
+//= link_tree ./images
+<% end -%>
+<% unless options.skip_javascript -%>
+//= link ./javascripts/application.js
+<% end -%>
+//= link ./stylesheets/application.css
+<% if mountable? && !api? -%>
+//= link <%= underscored_name %>_manifest.js
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js
new file mode 100644
index 0000000000..f8ef26982a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js
@@ -0,0 +1,6 @@
+<% if mountable? -%>
+<% unless options.skip_javascript -%>
+//= link ./javascripts/<%= namespaced_name %>/application.js
+<% end -%>
+//= link ./stylesheets/<%= namespaced_name %>/application.css
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
index 8913b40f69..e54c6461cc 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
@@ -5,7 +5,7 @@
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
index 0cdd2788d0..0ebd7fe829 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -7,7 +7,8 @@
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
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 95adcc06ff..b1b77629d4 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
@@ -20,6 +20,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
+ ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
ActiveSupport::TestCase.fixtures :all
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
index 896b38bc8f..f302cd6c3d 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -3,7 +3,10 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= controller_class_name %>ControllerTest < ActionController::TestCase
setup do
- @<%= singular_table_name %> = <%= table_name %>(:one)
+ @<%= singular_table_name %> = <%= fixture_name %>(:one)
+<% if mountable_engine? -%>
+ @routes = Engine.routes
+<% end -%>
end
test "should get index" do
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 41422a656c..78a857e0f1 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -26,7 +26,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
if part = find_part(part_type)
response.content_type = part_type
- render text: part.respond_to?(:decoded) ? part.decoded : part
+ render plain: part.respond_to?(:decoded) ? part.decoded : part
else
raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index ebcaaaba46..e47616a87f 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -123,6 +123,10 @@ module Rails
options[:load_path] ? load_path! : skip_load_path!
end
+ def absolute_current # :nodoc:
+ File.expand_path(@current, @root.path)
+ end
+
def children
keys = @root.keys.find_all { |k|
k.start_with?(@current) && k != @current
@@ -175,6 +179,10 @@ module Rails
@paths
end
+ def extensions # :nodoc:
+ $1.split(',') if @glob =~ /\{([\S]+)\}/
+ end
+
# Expands all paths against the root and return all unique values.
def expanded
raise "You need to set a path root" unless @root.path
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 9b058a1848..8dd87b6cc5 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -3,7 +3,7 @@
# rake notes
# rake notes:optimize
#
-# and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/tasks/annotations.rake</tt>.
+# and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>.
#
# Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
# represent the line where the annotation lives, its tag, and its text. Note
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 2c3d278eca..d3e33584d7 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,6 +3,7 @@ require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
+ dev
framework
initializers
log
@@ -10,8 +11,9 @@ require 'rake'
misc
restart
routes
- statistics
tmp
-).each do |task|
+).tap { |arr|
+ arr << 'statistics' if Rake.application.current_scope.empty?
+}.each do |task|
load "rails/tasks/#{task}.rake"
end
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
new file mode 100644
index 0000000000..e949172d3f
--- /dev/null
+++ b/railties/lib/rails/tasks/dev.rake
@@ -0,0 +1,15 @@
+namespace :dev do
+ task :cache do
+ desc 'Toggle development mode caching on/off'
+
+ if File.exist? 'tmp/caching-dev.txt'
+ File.delete 'tmp/caching-dev.txt'
+ puts 'Development mode is no longer being cached.'
+ else
+ FileUtils.touch 'tmp/caching-dev.txt'
+ puts 'Development mode is now being cached.'
+ end
+
+ FileUtils.touch 'tmp/restart.txt'
+ end
+end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index ab71298509..5552779ef1 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -48,4 +48,5 @@ module Minitest
mattr_accessor(:run_with_rails_extension) { false }
end
+Minitest.load_plugins
Minitest.extensions << 'rails'
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 36ab8109a7..fef65adda2 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -48,7 +48,7 @@ module ApplicationTests
assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
- test "assets aren't concatenated when compile is true is on and debug_assets params is true" do
+ test "assets are served with sourcemaps when compile is true and debug_assets params is true" do
add_to_env_config "production", "config.assets.compile = true"
ENV["RAILS_ENV"] = "production"
@@ -57,8 +57,7 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application(\.debug)?-([0-z]+)\.js"><\/script>/, last_response.body)
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index ba2c27d8e8..ddcd51f3fc 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -72,6 +72,7 @@ module ApplicationTests
end
test "precompile creates the file, gives it the original asset's content and run in production as default" do
+ app_file "app/assets/manifest.js", "//= link_tree ./javascripts"
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
@@ -87,6 +88,7 @@ module ApplicationTests
end
def test_precompile_does_not_hit_the_database
+ app_file "app/assets/manifest.js", "//= link_tree ./javascripts"
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/javascripts/foo/application.js", "alert();"
app_file "app/controllers/users_controller.rb", <<-eoruby
@@ -178,8 +180,11 @@ module ApplicationTests
end
test 'precompile use assets defined in app config and reassigned in app env config' do
- add_to_config 'config.assets.precompile = [ "something.js" ]'
- add_to_env_config 'production', 'config.assets.precompile += [ "another.js" ]'
+ add_to_config 'config.assets.precompile = [ "something_manifest.js" ]'
+ add_to_env_config 'production', 'config.assets.precompile += [ "another_manifest.js" ]'
+
+ app_file 'app/assets/something_manifest.js', '//= link ./javascripts/something.js'
+ app_file 'app/assets/another_manifest.js', '//= link ./javascripts/another.js'
app_file 'app/assets/javascripts/something.js.erb', 'alert();'
app_file 'app/assets/javascripts/another.js.erb', 'alert();'
@@ -190,14 +195,14 @@ module ApplicationTests
assert_file_exists("#{app_path}/public/assets/another-*.js")
end
- test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do
+ test "asset pipeline should use a Sprockets::CachedEnvironment when config.assets.digest is true" do
add_to_config "config.action_controller.perform_caching = false"
add_to_env_config "production", "config.assets.compile = true"
ENV["RAILS_ENV"] = "production"
require "#{app_path}/config/environment"
- assert_equal Sprockets::Index, Rails.application.assets.class
+ assert_equal Sprockets::CachedEnvironment, Rails.application.assets.class
end
test "precompile creates a manifest file with all the assets listed" do
@@ -246,7 +251,7 @@ module ApplicationTests
test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+ app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }"
add_to_env_config "test", "config.assets.digest = true"
precompile!('RAILS_ENV=test')
@@ -258,7 +263,7 @@ module ApplicationTests
test "precompile shouldn't use the digests present in manifest.json" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "p { url: <%= asset_path('rails.png') %> }"
+ app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }"
ENV["RAILS_ENV"] = "production"
precompile!
@@ -277,7 +282,7 @@ module ApplicationTests
test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
+ app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }"
ENV["RAILS_ENV"] = "production"
precompile!
@@ -289,7 +294,8 @@ module ApplicationTests
test "precompile should handle utf8 filenames" do
filename = "レイルズ.png"
app_file "app/assets/images/#{filename}", "not an image really"
- add_to_config "config.assets.precompile = [ /\.png$/, /application.(css|js)$/ ]"
+ app_file "app/assets/manifest.js", "//= link_tree ./images"
+ add_to_config "config.assets.precompile = %w(manifest.js)"
precompile!
@@ -394,7 +400,7 @@ module ApplicationTests
app_file "app/assets/javascripts/xmlhr.js.erb", "<%= Post.name %>"
precompile!
- assert_equal "Post;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first)
+ assert_equal "Post\n;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first)
end
test "initialization on the assets group should set assets_dir" do
@@ -439,9 +445,9 @@ module ApplicationTests
class ::PostsController < ActionController::Base; end
get '/posts', {}, {'HTTPS'=>'off'}
- assert_match('src="http://example.com/assets/application.self.js', last_response.body)
+ assert_match('src="http://example.com/assets/application.debug.js', last_response.body)
get '/posts', {}, {'HTTPS'=>'on'}
- assert_match('src="https://example.com/assets/application.self.js', last_response.body)
+ assert_match('src="https://example.com/assets/application.debug.js', last_response.body)
end
test "asset urls should be protocol-relative if no request is in scope" do
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 9ee54796a4..ab7f29b0f2 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -132,6 +132,79 @@ en:
assert_equal "2", last_response.body
end
+ test "new locale files are loaded" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+ assert_equal "1", last_response.body
+
+ # Wait a full second so we have time for changes to propagate
+ sleep(1)
+
+ remove_file "config/locales/en.yml"
+ app_file "config/locales/custom.en.yml", <<-YAML
+en:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_equal "2", last_response.body
+ end
+
+ test "I18n.load_path is reloaded" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.load_path.inspect]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+
+ assert_match "en.yml", last_response.body
+
+ # Wait a full second so we have time for changes to propagate
+ sleep(1)
+
+ app_file "config/locales/fr.yml", <<-YAML
+fr:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_match "fr.yml", last_response.body
+ assert_match "en.yml", last_response.body
+ end
+
# Fallbacks
test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
I18n.backend = Class.new(I18n::Backend::Simple).new
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
new file mode 100644
index 0000000000..28d8b22a37
--- /dev/null
+++ b/railties/test/application/rake/dev_test.rb
@@ -0,0 +1,35 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ module RakeTests
+ class RakeDevTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'dev:cache creates file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rake dev:cache`
+ assert File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is now being cached/, output)
+ end
+ end
+
+ test 'dev:cache deletes file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rake dev:cache`
+ output = `rake dev:cache`
+ assert_not File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is no longer being cached/, output)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index ba688f1e9e..7a063aeddf 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -44,6 +44,22 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
+ def test_caching_without_option
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal nil, options[:caching]
+ end
+
+ def test_caching_with_option
+ args = ["--dev-caching"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:caching]
+
+ args = ["--no-dev-caching"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal false, options[:caching]
+ end
+
def test_log_stdout
with_rack_env nil do
with_rails_env nil do
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 2857dae07e..fabba555ef 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'env_helpers'
-require 'minitest/mock'
class ActionsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -12,13 +11,11 @@ class ActionsTest < Rails::Generators::TestCase
def setup
Rails.application = TestApp::Application
- @mock_generator = Minitest::Mock.new
super
end
def teardown
Rails.application = TestApp::Application.instance
- @mock_generator.verify
end
def test_invoke_other_generator_with_shortcut
@@ -150,16 +147,13 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_git_with_symbol_should_run_command_using_git_scm
- @mock_generator.expect(:call, nil, ['git init'])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ['git init']) do
action :git, :init
end
end
def test_git_with_hash_should_run_each_command_using_git_scm
- @mock_generator.expect(:call, nil, ["git rm README"])
- @mock_generator.expect(:call, nil, ["git add ."])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, [ ["git rm README"], ["git add ."] ]) do
action :git, rm: 'README', add: '.'
end
end
@@ -185,15 +179,13 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_generate_should_run_script_generate_with_argument_and_options
- @mock_generator.expect(:call, nil, ['bin/rails generate model MyModel', verbose: false])
- generator.stub(:run_ruby_script, @mock_generator) do
+ assert_called_with(generator, :run_ruby_script, ['bin/rails generate model MyModel', verbose: false]) do
action :generate, 'model', 'MyModel'
end
end
def test_rake_should_run_rake_command_with_default_env
- @mock_generator.expect(:call, nil, ["rake log:clear RAILS_ENV=development", verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear'
end
@@ -201,15 +193,13 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_rake_with_env_option_should_run_rake_command_in_env
- @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
action :rake, 'log:clear', env: 'production'
end
end
def test_rake_with_rails_env_variable_should_run_rake_command_in_env
- @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "production" do
action :rake, 'log:clear'
end
@@ -217,8 +207,7 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_env_option_should_win_over_rails_env_variable_when_running_rake
- @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ['rake log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "staging" do
action :rake, 'log:clear', env: 'production'
end
@@ -226,8 +215,7 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_rake_with_sudo_option_should_run_rake_command_with_sudo
- @mock_generator.expect(:call, nil, ["sudo rake log:clear RAILS_ENV=development", verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear', sudo: true
end
@@ -235,8 +223,7 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_capify_should_run_the_capify_command
- @mock_generator.expect(:call, nil, ['capify .', verbose: false])
- generator.stub(:run, @mock_generator) do
+ assert_called_with(generator, :run, ['capify .', verbose: false]) do
action :capify!
end
end
@@ -274,8 +261,7 @@ F
def test_readme
run_generator
- 2.times { @mock_generator.expect(:call, destination_root,[]) }
- Rails::Generators::AppGenerator.stub(:source_root, @mock_generator) do
+ assert_called(Rails::Generators::AppGenerator, :source_root, times: 2, returns: destination_root) do
assert_match "application up and running", action(:readme, "README.md")
end
end
@@ -283,8 +269,7 @@ F
def test_readme_with_quiet
generator(default_arguments, quiet: true)
run_generator
- 2.times { @mock_generator.expect(:call, destination_root,[]) }
- Rails::Generators::AppGenerator.stub(:source_root, @mock_generator) do
+ assert_called(Rails::Generators::AppGenerator, :source_root, times: 2, returns: destination_root) do
assert_no_match "application up and running", action(:readme, "README.md")
end
end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 62ca0ecb4b..b19a5a7144 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/module/remove_method'
require 'active_support/testing/stream'
+require 'active_support/testing/method_call_assertions'
require 'rails/generators'
require 'rails/generators/test_case'
@@ -25,6 +26,7 @@ require 'action_view'
module GeneratorsTestHelper
include ActiveSupport::Testing::Stream
+ include ActiveSupport::Testing::MethodCallAssertions
def self.included(base)
base.class_eval do
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 6f5a815173..afccf9d885 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -313,6 +313,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/test_helper.rb" do |content|
assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content)
+ assert_match(/ActionDispatch::IntegrationTest\.fixture_path = ActiveSupport::TestCase\.fixture_pat/, content)
end
end
@@ -322,7 +323,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
assert_file "hyphenated-name/app/assets/images/hyphenated/name"
assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/
- assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
@@ -341,7 +342,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/
- assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
@@ -360,7 +361,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name"
assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name"
assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/
- assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\n end\nend/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\n end\nend/
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index a853bfbe21..0c3808a9a0 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -505,4 +505,32 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
+
+ def test_scaffold_tests_pass_by_default_inside_api_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable --api` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_scaffold_tests_pass_by_default_inside_api_full_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full --api` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 31a575749a..291415858c 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/model/model_generator'
require 'rails/generators/test_unit/model/model_generator'
-require 'minitest/mock'
class GeneratorsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -9,18 +8,15 @@ class GeneratorsTest < Rails::Generators::TestCase
def setup
@path = File.expand_path("lib", Rails.root)
$LOAD_PATH.unshift(@path)
- @mock_generator = MiniTest::Mock.new
end
def teardown
$LOAD_PATH.delete(@path)
- @mock_generator.verify
end
def test_simple_invoke
assert File.exist?(File.join(@path, 'generators', 'model_generator.rb'))
- @mock_generator.expect(:call, nil, [["Account"],{}])
- TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do
Rails::Generators.invoke("test_unit:model", ["Account"])
end
end
@@ -51,23 +47,20 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_should_give_higher_preference_to_rails_generators
assert File.exist?(File.join(@path, 'generators', 'model_generator.rb'))
- @mock_generator.expect(:call, nil, [["Account"],{}])
- Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do
warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] }
assert warnings.empty?
end
end
def test_invoke_with_default_values
- @mock_generator.expect(:call, nil, [["Account"],{}])
- Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do
Rails::Generators.invoke :model, ["Account"]
end
end
def test_invoke_with_config_values
- @mock_generator.expect(:call, nil, [["Account"],{behavior: :skip}])
- Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], behavior: :skip]) do
Rails::Generators.invoke :model, ["Account"], behavior: :skip
end
end
@@ -115,8 +108,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_invoke_with_nested_namespaces
model_generator = Minitest::Mock.new
model_generator.expect(:start, nil, [["Account"], {}])
- @mock_generator.expect(:call, model_generator, ['namespace', 'my:awesome'])
- Rails::Generators.stub(:find_by_namespace, @mock_generator) do
+ assert_called_with(Rails::Generators, :find_by_namespace, ['namespace', 'my:awesome'], returns: model_generator) do
Rails::Generators.invoke 'my:awesome:namespace', ["Account"]
end
model_generator.verify
@@ -185,8 +177,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_fallbacks_for_generators_on_invoke
Rails::Generators.fallbacks[:shoulda] = :test_unit
- @mock_generator.expect(:call, nil, [["Account"],{}])
- TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do
Rails::Generators.invoke "shoulda:model", ["Account"]
end
ensure
@@ -196,8 +187,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_nested_fallbacks_for_generators
Rails::Generators.fallbacks[:shoulda] = :test_unit
Rails::Generators.fallbacks[:super_shoulda] = :shoulda
- @mock_generator.expect(:call, nil, [["Account"],{}])
- TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ assert_called_with(TestUnit::Generators::ModelGenerator, :start, [["Account"], {}]) do
Rails::Generators.invoke "super_shoulda:model", ["Account"]
end
ensure
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 12630e4d01..96b54c7264 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -62,6 +62,13 @@ class PathsTest < ActiveSupport::TestCase
assert_equal ["/foo/bar/baz"], @root["app/models"].to_a
end
+ test "absolute current path" do
+ @root.add "config"
+ @root.add "config/locales"
+
+ assert_equal "/foo/bar/config/locales", @root["config/locales"].absolute_current
+ end
+
test "adding multiple physical paths as an array" do
@root.add "app", with: ["/app", "/app2"]
assert_equal ["/app", "/app2"], @root["app"].to_a
@@ -215,6 +222,12 @@ class PathsTest < ActiveSupport::TestCase
assert_equal "*.rb", @root["app"].glob
end
+ test "it should be possible to get extensions by glob" do
+ @root["app"] = "/app"
+ @root["app"].glob = "*.{rb,yml}"
+ assert_equal ["rb", "yml"], @root["app"].extensions
+ end
+
test "it should be possible to override a path's default glob without assignment" do
@root.add "app", with: "/app", glob: "*.rb"
assert_equal "*.rb", @root["app"].glob